Item 4: Make sure that objects are initialized before they're used.

出于效率原因,C++不保证内置类型数据成员的初始化。 但成员对象会在构造函数进入之前进行初始化。例如:

class C{
    int a;
    B b;
};

其中数据成员 a 是基本数据类型,会执行默认初始化,最终保有不确定的值。 b是对象类型,总会被初始化,它的无参数的构造函数将被调用。

有成员对象的类称为封闭类(Enclosing Class,这里 C 就是一个封闭类),如果B没有不带参数的构造函数,则必须在 C 的构造函数中进行 b 的初始化。

C++中变量的初始化规则较为复杂,大致的规则是这样的:

  • 对于 C 风格的C++:如果初始化会产生额外的负担,则不会初始化。
  • 对于面向对象风格的 C++:一般在构造函数上声明成员初始化列表(所以你也应该这样做!)。
  • 全局/静态变量会自动初始化,自动变量(栈里面的)不会自动初始化。

完整的讨论可以参考:C++手稿:那些变量会自动初始化?

构造函数上初始化所有成员

在 C++ 程序设计中的绝大多数场景下,由构造函数上进行变量的初始化。于是规则很简单:在构造函数上初始化所有成员。 值得注意的是,在构造函数进入之前成员已经进行了初始化。下面的代码会使得成员对象 b 被构造两次:

class C{
   B b;
public:
   C(){ b = B(); }  // 这个是赋值啦
};

因此正确的做法是在构造函数上声明成员初始化列表

class C{
    B b;
    int i;
public:
    C():b(), i(){}
};
// 或者
C::C() : b(), i(1){}

在构造函数前总是列出所有成员变量,以免遗漏。

类静态变量的初始化

类的静态变量除了在类声明中进行声明外,还需要在类声明外进行定义。 (static const int是个例外,参见:Effective C++笔记:避免使用define

静态变量的生命周期不同于栈或者堆中的对象,从它被构造开始一直存在,直到程序结束。 包括全局变量、命名空间下的变量、类中和函数中定义的static对象。 其中,定义在函数中的称为 local static,其他的称为 non-local static。

多个编译单元的 non-local static 对象初始化次序是不确定的。因此可能会在使用时造成未初始化的问题, 解决办法便是把它变成一个 local static,C++保证了在第一次函数调用遇到该 local static 声明时, 它会被初始化。这也是Singleton(local static) 的典型实现:

class FileSystem{...};
FileSystem& tfs(){
		//将在首次进入函数时构造
		static FileSystem fs;	
		return fs;
}

以下提供较完整的Signleton C++实现:

class Singleton{
private:
    Singleton(){}
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton& rhs);
public:
    static Singleton& getInstance(){
        static Singleton instance;
        return instance; 
    }
};

一旦声明了任何形式的构造函数(包括拷贝构造函数),编译器将不会生成默认的无参构造函数。所以上述的无参数的构造函数可以省略。

本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2015/07/22/effective-cpp-4.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。