0
点赞
收藏
分享

微信扫一扫

换个花样玩C++(7) 你以为的C++类成员变量的初始化顺序太简单?


我们都知道,C++类成员变量通过构造函数体初始化时,初始化顺序由构造函数体中的变量初始化顺序决定,与类成员变量的定义顺序无关系, 关于如何初始化,巴拉巴拉的我就不多说了,就说大家容易踩的雷区 

类成员内部初始化+初始化列表中初始化

在初始化列表中初始化引用对象

 为什么我建议使用初始化列表初始化类成员

下面这段代码,你很容易能确定结果:

示例1

#include <iostream> 
using namespace std;
class Sample {
private:
	int m_b;
	int m_a;
public:
	Sample(int i) :m_a(i), m_b(m_a + 1) {
		cout << m_a << "," << m_b << endl;
	}
	~Sample() {
	}
};
int main()
{   
	Sample s(10);
}

输出结果:10,-858993459 (不同编译器对于m_b的值输出有差异,gcc结果可能是10,32766)

类成员内部初始化+初始化列表中初始化

那我再做个改动,给两个类成员变量在类内部初始化值为0,结果又会是多少呢?

示例2

#include <iostream> 
using namespace std;
class Sample {
private:
	int m_b=0;
	int m_a=0;
public:
	Sample(int i) :m_a(i), m_b(m_a + 1) {
		cout << m_a << "," << m_b << endl;
	}
	~Sample() {
	}
};
int main()
{   
	Sample s(10);
}

 m_a和m_b两个类成员变量的值会是多少呢?

其实输出结果和刚才一样,为什么呢?

C++11类内部初始化,优先于任何构造函数初始化成员变量。内部初始化后,如果构造函数不显式重新初始化成员变量默认值,成员变量将保持内部初始化值默认值;如果构造函数显示重新初始化成员变量默认值,成员变量将保持构造函数重新赋值。
如果初始化列表和构造函数体同时对一个变量进行了初始化,列表初始化会优先得到执行,接着才会执行构造函数体中的变量初始化。

在C++11之前,创建结构体/类只是开辟了一块内存空间,而不赋初始值,赋初始值有的编译器不支持,因此从C++11之后,编译器就支持在结构体/类的定义时进行成员初始化。

在初始化列表中初始化引用对象

引用成员变量我在上一节讲到了,这一节我们再次引申和升华一次,来看这段代码,你觉得会输出什么?

示例3:

#include <iostream> 
using namespace std;

class Sample {
private:
	int m_b;
	int m_a;
public:
	Sample(int i) :m_a(i), m_b(i) {
		cout << m_a << "," << m_b << endl;
	}
	~Sample() {

	}
};

class Test {
public:
	Sample& m_s;
	Test(int i) :m_s(i) {
	}
};
int main()
{ 
     cout << sizeof(Test) <<endl;
}


如果你看走眼了,你以为会输出8,或者4字节的整数倍,可惜这次并不是你想的这样,编译阶段报错了:


/workspace/C1/main.cpp:20:15: error: non-const lvalue reference to type 'Sample' cannot bind to a value of unrelated type 'int'
         Test(int i) :m_s(i) {
                      ^   ~
 1 error generated.

怎么回事呢?请记住,Sample& m_s是一个引用,而不是指针或变量,因此m_s作为引用成员将引用一个Sample类型的对象,而不是直接构造Sample对象,所以,应该怎么改呢?

我们应该在构造函数的参数里传入一个Sample对象,让m_s引用入参的Sample对象即可。

class Test {
public:
	Sample& m_s;
	Test(Sample& s) :m_s(s) {
	}
};

 完整代码如下:

#include <iostream> 
using namespace std;

class Sample {
private:
	int m_b;
	int m_a;
public:
	Sample(int i) :m_a(i), m_b(i) {
		cout << m_a << "," << m_b << endl;
	}
	~Sample() {

	}
};

class Test {
public:
	Sample& m_s;
	Test(Sample& s) :m_s(s) {
	}
};
int main()
{   
	Sample s(1);
	Test t(s);
	cout << sizeof(Test) << endl;
}

 为什么我建议使用初始化列表初始化类成员

在学习过程中,经常有人说初始化列表初始化类成员的效率高。是不是这样的呢?

首先我们把类成员变量按类型分类:

1.基本数据类型,复合类型(指针,引用)
2.用户定义类型(类、结构体类型)

分情况说明:

对于1,在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
对于2,结果上相同,但是性能上存在很大的差别
初始化列表,顾名思义,是对成员数据进行初始化,只需要一次拷贝构造函数即可,而赋值呢,你会发现既要调用其构造函数又要调用赋值运算符重载函数,多了一次操作。

我们来举个例子:

我们先用构造函数体内进行赋值操作

#include <iostream>
using namespace std;
class Sample {
public:
    Sample() { cout << "Sample()" << endl; }
    Sample(const Sample& a) { cout << "copy Sample()" << endl; }
    Sample(const Sample&& a) { cout << "copy Sample(&)" << endl; }
    ~Sample() { cout << "~Sample()" << endl; }
    Sample& operator=(const Sample& a) {
        cout << "operator=" << endl;
        return *this;
    }
};

class Test
{
public:
    Test(Sample& a)  {m_s=a;}
private:
    Sample m_s;
};

int main()
{
    Sample a;
    cout << "construct Test" <<endl;
    Test t(a);
}

输出结果:

Sample()
construct Test
Sample()
operator=
~Sample()
~Sample()

如果我们使用初始化列表呢,做个对比:

#include <iostream>
using namespace std;
class Sample {
public:
    Sample() { cout << "Sample()" << endl; }
    Sample(const Sample& a) { cout << "copy Sample()" << endl; }
    Sample(const Sample&& a) { cout << "copy Sample(&)" << endl; }
    ~Sample() { cout << "~Sample()" << endl; }
    Sample& operator=(const Sample& a) {
        cout << "operator=" << endl;
        return *this;
    }
};

class Test
{
public:
    Test(Sample& a) : m_s(a) {}
private:
    Sample m_s;
};

int main()
{
    Sample a;
    cout << "construct Test" <<endl;
    Test t(a);
}

结果:

Sample()
construct Test
copy Sample()
~Sample()
~Sample()

是不是对比下来少了一次函数调用呢。比如游戏服务器在创建十几万甚至几百万的玩家,还有相关活动,战斗副本等数据的时候,使用赋值操作来初始化类成员而不是用参数列表,你可以想象到你需要额外增加多少次函数调用。 

举报

相关推荐

0 条评论