对于内置类型,必须手工完成这件事。
对于其他类型,则依赖于这个类型的构造函数。
有一种情况下我们也可以考虑在构造函数体内部完成赋值。因为我们知道发现,对于内置类型,使用初始化列表和赋值的开销是一样的。假如我们有多个构造函数,每个函数都要初始化某几个内置的数据类型,那么我们可以选择把这几个数据类型的赋值初始化写成一个private函数,然后在这些构造函数中调用这个函数。
最后讨论的是一个特殊的问题:不同编译单元内定义的non-local static对象的初始化顺序。
首先,什么是编译单元?简单的说,就是源文件+它包含的头文件,他俩和在一起被编译成一个.o或者.obj。
那什么是non-local static对象?首先,static对象是那些寿命从被构造出来到程序结束为止的对象。它包括:全局对象,定义在namespace作用域的对象,类内、以及在文件作用域中被声明为static的对象。其中函数内部的对象成为local static对象,其他的成为non-local static。
有了这些概念,我们就可以说明这个问题了,先看一个简单的示例程序:
值得注意的是Test1的构造函数中调用了t2的test2函数。它们的定义在对应的源文件中:
主程序其实没啥:
运行结果为:
Test1 构造函数
Test2测试函数
Test2 构造函数
hello
Test2析构函数
Test1析构函数
请按任意键继续. . .
我们发现,对于两个全局变量t1和t2。t1竟然可以在t2还没有初始化的时候调用t2的函数!这里的函数只不过是打印,所以无所谓,但如果是处理数据之类的,可就出问题了!这是因为这两个不同编译单元的non-local static对象初始化的顺序是不定的。
那么问题如何解决呢?其实很简单,就是将每个non-local static对象搬到自己的专属函数里,这些函数返回一个指向它所含对象的引用。
这次还是那个主函数,注意到这次只有一个全局变量newTest1
Test1 构造函数
Test2 构造函数
Test2测试函数
hello
Test1析构函数
Test2析构函数
请按任意键继续. . .
但是,在调用全局变量newTest1的构造函数时,就会调用Test2的构造函数,避免了Test2的对象还未初始化就能调用它的成员函数的情况。这是因为函数内的local static对象会在该函数被调用期间,首次遇上该对象的定义式时被初始化。
总结起来就是3句话:
1.内置变量手工初始化。
2.类的数据最好通过初始化列表初始化,而不是在构造函数体内赋值。
3.为了解决不同编译单元的non-local static对象初始化顺序问题,最好以local static对象替换non-local static对象。
对于其他类型,则依赖于这个类型的构造函数。
首先,最好使用初始化列表而不是在函数体内部进行赋值。原因有两个
1.有些对象只能初始化,不能赋值:举一个简单的例子:const变量。
2.在构造函数体中赋值前,其实已经进行了初始化操作。对于内置类型,这里初始化的都是一些垃圾数字,然后才是赋值。对于自定义类型,先是调用了这个类型的默认构造函数,进行初始化,然后在利用重载的的赋值操作符进行赋值。如果你使用初始化列表,就不用赋值了,节省了一比开销。但要注意的是,初始化列表中初始化的顺序与类的定义中顺序相同,而与列表中的顺序无关。有一种情况下我们也可以考虑在构造函数体内部完成赋值。因为我们知道发现,对于内置类型,使用初始化列表和赋值的开销是一样的。假如我们有多个构造函数,每个函数都要初始化某几个内置的数据类型,那么我们可以选择把这几个数据类型的赋值初始化写成一个private函数,然后在这些构造函数中调用这个函数。
最后讨论的是一个特殊的问题:不同编译单元内定义的non-local static对象的初始化顺序。
首先,什么是编译单元?简单的说,就是源文件+它包含的头文件,他俩和在一起被编译成一个.o或者.obj。
那什么是non-local static对象?首先,static对象是那些寿命从被构造出来到程序结束为止的对象。它包括:全局对象,定义在namespace作用域的对象,类内、以及在文件作用域中被声明为static的对象。其中函数内部的对象成为local static对象,其他的成为non-local static。
有了这些概念,我们就可以说明这个问题了,先看一个简单的示例程序:
我在头文件中定义了两个类,并声明了两个全局对象:
class Test1
{
public:
Test1();
~Test1(){cout<<"Test1析构函数"<<endl;}
};
class Test2
{
public:
Test2(){cout<<"Test2 构造函数"<<endl;}
~Test2(){cout<<"Test2析构函数"<<endl;}
void test2(){cout<<"Test2测试函数"<<endl;}
};
extern Test2 t2;
extern Test1 t1;
值得注意的是Test1的构造函数中调用了t2的test2函数。它们的定义在对应的源文件中:
//t1.cpp
#include "item2.h"
Test1::Test1()
{
cout<<"Test1 构造函数"<<endl;
t2.test2();
}
//全局变量
Test1 t1;
//t2.cpp
#include "item2.h"
Test2 t2;
主程序其实没啥:
int main()
{
cout<<"hello"<<endl;
return 0;
}
运行结果为:
Test1 构造函数
Test2测试函数
Test2 构造函数
hello
Test2析构函数
Test1析构函数
请按任意键继续. . .
我们发现,对于两个全局变量t1和t2。t1竟然可以在t2还没有初始化的时候调用t2的函数!这里的函数只不过是打印,所以无所谓,但如果是处理数据之类的,可就出问题了!这是因为这两个不同编译单元的non-local static对象初始化的顺序是不定的。
那么问题如何解决呢?其实很简单,就是将每个non-local static对象搬到自己的专属函数里,这些函数返回一个指向它所含对象的引用。
//t2.cpp
#include "item2.h"
Test2& GetT2()
{
static Test2 t2;
return t2;
}
//t1.cpp
Test1& GetT1()
{
static Test1 t1;
return t1;
}
Test1::Test1()
{
cout<<"Test1 构造函数"<<endl;
GetT2().test2();
}
Test1 newTest1;
这次还是那个主函数,注意到这次只有一个全局变量newTest1
Test1 构造函数
Test2 构造函数
Test2测试函数
hello
Test1析构函数
Test2析构函数
请按任意键继续. . .
但是,在调用全局变量newTest1的构造函数时,就会调用Test2的构造函数,避免了Test2的对象还未初始化就能调用它的成员函数的情况。这是因为函数内的local static对象会在该函数被调用期间,首次遇上该对象的定义式时被初始化。
总结起来就是3句话:
1.内置变量手工初始化。
2.类的数据最好通过初始化列表初始化,而不是在构造函数体内赋值。
3.为了解决不同编译单元的non-local static对象初始化顺序问题,最好以local static对象替换non-local static对象。