Effective C++ 04:确定对象被使用前已先被初始化

来源:互联网 发布:lsb算法 delphi 编辑:程序博客网 时间:2024/05/16 10:59
        C++的对象在声明的时候是无初值的,为了避免在对象初始化之前过早的使用它们,你需要做三件事情:
        第一,手工初始化内置类型的变量。
        第二,使用成员初始化列表来初始化自定义类的对象的所有成分。
        第三,在“初始化次序不确定性”氛围下加强你的设计。

        首先,不要混淆了赋值和初始化,举一个例子
class PhoneNumber {
public:
PhoneNumber(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phone);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
 
PhoneNumber::PhoneNumber(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phone)
{
theName = name;//赋值,而非初始化
theAddress = address;//赋值,而非初始化
thePhones = phone;//赋值,而非初始化
numTimesConsulted = 0;
}
        c++规定,对象成员的初始化动作发生在进入构造函数本体之前,也就是说,theName,theAddress,thePhones都不是初始化,它们在构造函数内是被赋值了。它们的初始化发生的时间要在程序进入PhoneNumber构造函数之前,因为它们的类型string、list都是在stl库里面定义的,所以在进入构造函数之前,会先调用他们的default构造函数初始化一个对象,然后在PhoneNumber构造函数内进行赋值,这样的话相当于default构造函数所做的工作都是无用功,是一个影响效率的地方,所以,推荐的写法应该是:
PhoneNumber::PhoneNumber(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phone)
:theName(name),
theAddress(address),
thePhones(phone),
numTimesConsulted(0)
{ }
        使用构造函数初始化列表来初始化类的成员。此时theName以name为初值进行copy构造,不再进行default构造。
        对于大多数类型来说(非C++的内置类型)来说,比起调用default构造函数然后再用copy assignment操作符,只调用一次copy构造函数是比较高效的(这些函数和操作符的概念可以参照05节)。对于内置类型(numTimesConsulted),初始化和赋值操作的效率几乎相同,为了代码的一致性,也通过初始化列表来初始化。更进一步,为了防止某些变量初始化被遗漏,我们需要立下规则:总是在初始化列表中列出所有的成员变量。举个例子,如果觉得内置类型numTimesConsulted可以在后期赋值而不将其写入列表,它就没有初值,开启“不明确行为”的潘多拉盒子。更重要的是,如果内置类型是一个const类型的变量,它一定要初始化,而初始化一定要在进入构造函数本体之前(这也就解释了为什么类的const成员变量需要在初始化列表中初始化),所以,总是在构造函数初始化列表里初始化类的所有成员变量是很有必要的。

        C++有着十分固定的初始化次序,基类总是在派生类之前初始化,类的成员变量总是依其声明的次序被初始化,即使它们在初始化列表中出现的次序混乱也不会有任何影响。


“不同编译单元内定义之non-local static对象的初始化次序”
        static对象包括global对象,定义于namespace作用域内的对象,在class、函数、文件作用域内被声明为static的对象。函数内的static对象被称为local static对象,其他的则被称为non-local static对象。他们的析构函数都会在main()结束时被自动调用。(这些对象都是存储在全局/静态存储区)
        现在若有两个源文件a.hpp和b.hpp,每个中都含有non-local static对象,b中的对象会调用a中的对象的成员函数,也就是说,a中的对象一定要在b中的对象的函数执行之前初始化,但是由于a、b是两个不同的源码文件,c++对它们的初始化次序并无明确定义(这是有原因的:觉得两个文件内对象的初始化次序相当困难,根本无解),所以会出现访问未初始化的对象!!
class FileSystem {
public:
std::size_t numDisks() const;
};
extern FileSystem tfs;
class Directory {
public:
Directory ();
~Directory ();
private:
std::size_t disks;
};
 
Directory::Directory() {
disks = tfs.numDisks();    //tfs不一定被初始化
}
        c++保证,函数内的local static对象会在“该函数被调用期间”“首次遇上该对象之定义式”时被初始化。所以我们可以以“函数调用返回一个指向static对象的引用”替换“直接访问non-local static对象”。
class FileSystem {
public:
std::size_t numDisks() const;
};
FileSystem & tfs() {
static FileSystem fs;
return fs;
}
class Directory {
public:
Directory ();
~Directory ();
private:
std::size_t disks;
};
 
Directory::Directory() {
disks = tfs().numDisks(); //tfs()替换了tfs
}


请记住:
  • 为内置型对象手工初始化,因为C++不保证初始化它们。
  • 构造函数最好使用成员初始化列表,不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
  • 为免除“跨编译单元之初始化次序”问题,以local static对象替换non-local static对象。
0 0
原创粉丝点击