首先,这篇文章实际上不是我自己的话。它摘自一本很棒的书
"Effective C++:改善程序和设计的 55 个具体方法",作者是
Scott Meyers,我认为有必要为它写一篇文章,因为它提供了一些非常有用的信息。
尽量不要纠结于前几句话,因为这是章节中的一部分...
对于几乎所有其他事情,初始化的责任都落在构造函数上。那里的规则很简单:确保所有构造函数都初始化对象中的所有内容。
这个规则很容易遵循,但重要的是不要将赋值与初始化混淆。考虑一个表示地址簿中条目的类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
Class PhoneNumber { … };
Class ABEntry { // ABEntr = “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(const std::String& name, const std::string& address,
const std::list<PhoneNumber>& phones)
{
theName = name; // these are all assignments,
theAddress = address; // no initializations
thePhones = phones;
numTimesConsulted = 0;
}
|
这将产生具有您期望值的 ABEntry 对象,但这仍然不是最好的方法。C++ 的规则规定,对象的数据成员
在进入构造函数体
之前被初始化。在 ABEntry 构造函数内部,theName、theAddress 和 thePhones 没有被初始化,它们正在被赋值。初始化发生在更早的时候——当它们的默认构造函数在进入 ABEntry 构造函数体之前被自动调用时。对于 numTimesConsulted 来说,情况并非如此,因为它是一个内置类型。对于它,不能保证在赋值之前被初始化。
编写 ABEntry 构造函数的更好方法是使用成员初始化列表而不是赋值
1 2 3 4 5 6 7
|
const std::String& name, const std::string& address,
const std::list<PhoneNumber>& phones)
: theName(name),
theAddress(address), // these are now all initializations
thePhones(phones),
numTimesConsulted(0)
{} // the ctor body is now empty
|
此构造函数产生与上面相同的结果,但通常效率更高。基于赋值的版本首先调用默认构造函数来初始化 theName、theAddress 和 thePhones,然后迅速在默认构造的对象之上分配新值。因此,在这些默认构造中执行的所有工作都被浪费了。成员初始化列表方法避免了这个问题,因为初始化列表中的参数用作各种数据成员的构造函数参数。在这种情况下,theName 从 name 复制构造,theAddress 从 address 复制构造,thePhones 从 phones 复制构造。对于大多数类型,对复制构造函数的单次调用比调用默认构造函数后跟调用复制赋值运算符更有效——有时
效率高得多。
对于像 numTimesConsulted 这样的内置类型的对象,初始化和赋值的成本没有差异,但为了保持一致性,通常最好通过成员初始化来初始化所有内容。同样,即使您想默认构造一个数据成员,也可以使用成员初始化列表;只需指定没有任何初始化参数即可。