条款4:确保对象在使用之前被初始化

文章目录

  • 内置类型
  • 非局部静态对象
  • 总结

内置类型

 C/C++ 中运行无初值的对象,是否确保对象初始化的规则很复杂。一般来说,如果使用C++的C部分,并且初始化可能会导致运行时成本,则不能保证会发生。如果使用C++的非C部分,情况有时会不同。例如:数组不一定保证其内容初始化,而vector的内容必须初始化。
最佳方法是始终在使用对象之前初始化它们。对于内置类型的非成员对象,需要手动完成。如:

int x = 0; // 手动初始化int类型
const char * text = "A C-style string"; // 指针的手动初始化
double d; // 通过读取输入流来“初始化”
std::cin >> d; 

对于几乎所有其他的情况下,初始化的责任都落在构造函数身上。

class PhoneNumber { ... };
class ABEntry { // ABEntry = "Address Book Entry"
public:
    ABEntry(const std::string& name, const std::string& address,
        const std::list<PhoneNumber>& phones);
private:
    std::string theName;
    std::string theAddress;
    std::list<PhoneNumber> thePhones;
    int numTimesConsulted;
};
ABEntry::ABEntry(const std::string& name, const std::string& address,
    const std::list<PhoneNumber>& phones){
    theName = name; // 这些都是赋值而不是初始化,
    theAddress = address; 
    thePhones = phones;
    numTimesConsulted = 0;
}

 初始化发生在更早的时候——它们的默认构造函数在进入ABEntry构造函数体之前被自动调用。numTimesConsulted是个例外。内置类型不能保证在赋值之前对它进行了初始化。
下面是更好(更高效)的处理办法:

ABEntry::ABEntry(const std::string& name, const std::string& address,
    const std::list<PhoneNumber>& phones)
    : theName(name),
    theAddress(address), // 现在都是初始化
    thePhones(phones),
    numTimesConsulted(0)
{} // 现在函数体是空的

数据成员按照在类中声明的顺序初始化。与初始化列表中的顺序无关。

非局部静态对象

什么是非局部静态对象?

  • 这些对象声明在函数外部或类的静态成员中。
  • 生命周期贯穿整个程序运行期间,直到程序结束才被销毁。
  • 非局部静态对象通过 static 关键字在全局作用域内声明,或者在类中声明静态成员变量。
static int globalStaticVar; // 全局静态变量
class Example {
public:
    static int staticMember; // 类的静态成员变量
};

局部静态对象

  • 这些对象声明在函数或块的内部。
  • 生命周期从第一次使用到程序结束之间,在声明的函数中不同于非静态局部对象,它不会在函数退出时销毁,而是保持状态直到程序结束。
  • 声明时使用 static 关键字。
void exampleFunction() {
    static int localVar = 0; // 静态局部变量
    ++localVar;
}

 直接使用非局部静态对象的时候可能会存在一个问题,问题:如果一个编译单元中的非局部静态对象的初始化使用了另一个编译单元中的非局部静态对象,那么它所使用的对象可能是未初始化的,因为不同编译单元中定义的非局部静态对象初始化的相对顺序是未定义的。

class FileSystem { // 从你的库
public:
    ...
    std::size_t numDisks() const; // 众多成员函数之一
    ...
};
FileSystem tfs; 

extern FileSystem tfs; 	// 供客户(使用者)使用的对象;
				// "tfs" = "the file system"

现在假设某些客户创建了一个新类,用于处理文件系统中的目录。很自然,它们的类使用tfs对象:

class Directory { // 由库的客户(使用者)创建
public:
    Directory(params);
    ...
};
Directory::Directory(params)
{
    ...
    std::size_t disks = tfs.numDisks(); // 使用tfs对象
    ...
}
Directory tempDir(params); // 临时文件目录

除非tfs在tempDir之前初始化,否则tempDir的构造函数将尝试在tfs初始化之前使用它。但它们的顺序无法确定。
解决办法:将非局部静态对象替换为局部静态对象。
将每个非局部静态对象移动到自己的函数中,并将其声明为static。这些函数返回它们包含的对象的引用。客户调用函数而不是引用对象。
依据:C++保证,函数内的局部静态对象会在调用该函数时、第一次遇到该对象的定义时初始化

class FileSystem { ... }; // 和以前一样

FileSystem& tfs() // 这将替换tfs对象;在FileSystem类中,它可以是静态的
{ 
    static FileSystem fs; 	// 定义/初始化一个局部静态对象
    return fs; 		// 返回对它的引用
}
class Directory { ... }; // 和以前一样
Directory::Directory(params) // 与之前一样,除了对TFS的引用现在变为tfs ()
{  
    ...
    std::size_t disks = tfs().numDisks();
    ...
}
Directory& tempDir() // 这将替换tempDir对象;它可以是Directory类中静态函数
{ 
    static Directory td; 	//定义/初始化局部静态对象
    return td; 		// 返回指向它的引用
}

在多线程程序中,最好还是在单线程启动阶段手动调用函数完成初始化。

总结

  • 手动初始化内置类型的对象,因为C++不确保会初始化它们。
  • 在构造函数中,优先使用成员初始化列表,而不是在构造函数体内赋值。以类中声明的顺序在初始化列表中列出数据成员。
  • 通过将非局部静态对象替换为局部静态对象,避免跨编译单元的初始化顺序问题。

你可能感兴趣的:(Effective,C++,c++,c++)