【effective C++】04、确定对象被使用前已先被初始化

本篇博客主要是对下面两方面内容的理解:
1、C++中哪些对象一定被初始化,哪些对象并不一定被初始化:
答:对于C++中的C语言部分,初始化可能会带来运行期的成本,这时就不保证发生初始化,但是在C++的非C语音部分,规则有发生变化。

例如:C++语言的C部分中的array不保证其内容进行初始化,但是C++语言的STL部分中的vector却保证对其元素进行初始化操作。

2、对于并不一定被初始化的对象,我们要人为的对其进行初始化,而初始化的方法和注意事项有哪些。

1、变量初始化方法

对无任何成员的内置类型,必须手动初始化:

int x = 0;          
const char* text = "A C-style string";

double d;
std::cin>>d;    //可以通过读取input stream的方式完成初始化

内置类型以外的使用构造函数对其进行初始化:
构造函数初始化成员变量两种方法:
<1>构造函数内赋值

class Test{
    private:
        int _x;
    public:
        Test(int x){ //构造函数内部赋值
            _x = x;
        }
};

<2>成员初始化列表

class Text{
    private:
        int _x;
    public:
        Text(int x):_x(x){}//参数初始化列表进行初始化
};

上面的两种初始化结果相同,但是第二个效率较高,基于赋值的版本,先调用默认的构造函数为成员变量设置初始值,然后立刻再对它们赋予新的值,这样调用默认构造函数的操作就被白白的浪费了。而成员初始化列表中针对各个成员设置的实参,被用作各个成员变量构造函数的实参。

成员初始化列表 KO 构造函数内赋值:
1、效率更高:正如上面所说的,成员初始化列表只调用一次拷贝构造函数,相对于构造函数内赋值时先调用默认的构造函数再调用拷贝构造函数有更高的效率;内置类型对象其初始化和赋值的成本相同。

2、对象的成员变量的初始化发生在构造函数本体之前,严格意义上讲构造函数内赋值并不是初始化而是赋值操作。

3、有些情况下只能用成员初始化列表。对于const修饰的成员变量的初始化,引用成员的初始化,子类调用父类的构造函数初始化父类的成员。基类成员初始化早于派生类成员,而类内部的成员变量的初始化顺序与其声明的顺序相同。

2、跨编译单元的初始化问题

如果遵循以上的初始化策略,那么只有一个问题需要注意,那就是:不同编译单元内定义的non-local static对象,C++对于定义不同编译单元内的non-local static对象的初始化次序并没有明确定义。
1、local static对象:在函数内的static对象;
2、non-local static对象:global对象、定义于namespace作用域内的对象、在class内的对象、file作用域内被声明为static的对象。
3、编译单元:生成单一目标文件的源码,基本上是单一源码文件加上其所含入的头文件。
初始化次序遇到的问题,例如:
头文件定义了两个类:test.h

using namespace std;
class Test1 {
    public:
        Test1();
};

class Test2 {
    public:
        Test2(){ cout<<"Test2的构造函数" << endl; }
        void test2(){ cout << "测试函数" << endl; }
};

test1.cpp:

#include"test.h"
extern test2 test2;
Test1::test1(){
    cout<<"Test1的构造函数"<<endl;
    t2.test2();
}
Test1 test1;
int main(){
    system("pause");
    return 0;
}

Test2.cpp:

#include"test.h"
Test2 test2;

输出的测试结果:

Test1的构造函数
测试函数
Test2的构造函数

上面出现了一个问题就是:在test2初始化之前(调用test2构造函数之前),调用了Test2的成员函数,如果函数中有对Test2的成员对象test2的操作时,程序就会出现问题。

解决上面跨文件成员对象初始化顺序的问题:将每一个non-local static对象搬到自己的专属函数中,该对象在函数中被声明为static,函数返回一个指向对象的引用,然后用户调用这个函数而不是直接对对象进行操作。
将Test1.cpp改为:

#include"test.h"
extern Test2 test2;
extern Test2& getTest2();
Test1& getTest1(){
    static Test1 test1;
    return test1;
}
Test1::test1(){
    cout<<"Test1的构造函数"<<endl;
    getTest2().test2();
}
Test1 test1 = getTest1();
int main(){
    system("pause");
    return 0;
}

将Test2.cpp改为:

#include"test.h"
Test2& getTest2(){
    static Test2 test2;
    return test2;
}

测试的输出:

Test1的构造函数
Test2的构造函数
测试函数

3、总结

1、为内置没醒进行手工的初始化,因为C++并不保证初始化它们;
2、构造函数最好使用成员初始化列表,而不是在构造函数被体内使用赋值操作,初始化列表列出的成员变量,其排列次序应该和它在class中的声明的次序相同;
3、为了避免跨编译单元初始化次序的问题,请以local static对象替换non-local static对象。

猜你喜欢

转载自blog.csdn.net/u013108511/article/details/80470754