# Item 5: 了解 C++ 为你偷偷地加上和调用了什么函数
作者:Scott Meyers
译者:fatalerror99 (iTePub's Nirvana)
发布:http://blog.csdn.net/fatalerror99/
一个 empty class(空类)什么时候将不再是 empty class(空类)?答案是当 C++ 搞定了它。如果你自己不声明一个 copy constructor(拷贝构造函数),一个 copy assignment operator(拷贝赋值运算符)和一个 destructor(析构函数),编译器就会为这些东西声明一个它自己的版本。此外,如果你自己根本没有声明 constructor(构造函数),编译器就会为你声明一个 default constructor(缺省构造函数)。所有这些函数都被声明为 public 和 inline(参见 Item 30)。作为结果,如果你写
```
class Empty{};
```
在本质上和你这样写是一样的:
```
class Empty {
public:
Empty() { ... } // default constructor
Empty(const Empty& rhs) { ... } // copy constructor
~Empty() { ... } // destructor — see below
// for whether it's virtual
Empty& operator=(const Empty& rhs) { ... } // copy assignment operator
};
```
这些函数只有在它们被需要的时候才会生成,但是并不需要做太多的事情,就会用到它们。下面的代码会促使每一个函数生成:
```
Empty e1; // default constructor;
// destructor
Empty e2(e1); // copy constructor
e2 = e1; // copy assignment operator
```
假设编译器为你写了这些函数,那么它们做些什么呢?default constructor(缺省构造函数)和 destructor(析构函数)主要是给编译器一个地方放置 "behind the scenes" code(“幕后”代码)的,诸如 base classes(基类)和 non-static data members(非静态数据成员)的 constructors(构造函数)和 destructor(析构函数)的调用。注意,生成的 destructor(析构函数)是 non-virtual(非虚拟)的(参见 Item 7),除非它所在的 class(类)是从一个 base class(基类)继承而来,而 base class(基类)自己声明了一个 virtual destructor(虚拟析构函数)(这种情况下,函数的 virtualness(虚拟性)来自 base class(基类))。
对于 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符),compiler-generated versions(编译器生成版本)只是简单地从 source object(源对象)拷贝每一个 non-static data member(非静态数据成员)到 target object(目标对象)。例如,考虑一个 NamedObject template(模板),它允许你将名字和类型为 T 的 objects(对象)联系起来的:
```
template<typename T>
class NamedObject {
public:
NamedObject(const char *name, const T& value);
NamedObject(const std::string& name, const T& value);
...
private:
std::string nameValue;
T objectValue;
};
```
因为 NamedObject 中声明了一个 constructors(构造函数),编译器就不会再生成一个 default constructor(缺省构造函数)。这一点很重要,它意味着如果你小心地设计一个 class(类),使它需要 constructor arguments(构造函数参数),你就不必顾虑编译器会不顾你的决定,轻率地增加一个不需要参数的 constructors(构造函数)。
NamedObject 既没有声明 copy constructor(拷贝构造函数)也没有声明 copy assignment operator(拷贝赋值运算符),所以编译器将生成这些函数(如果需要它们的话)。看,这就是 copy constructor(拷贝构造函数)的用法:
```
NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1); // calls copy constructor
```
编译器生成的 copy constructor(拷贝构造函数)一定会用 no1.nameValue 和 no1.objectValue 分别初始化 no2.nameValue 和 no2.objectValue。nameValue 的类型是 string,标准 string 类型有一个 copy constructor(拷贝构造函数),所以将通过以 no1.nameValue 作为参数调用 string 的 copy constructor(拷贝构造函数)初始化 no2.nameValue。而另一方面,NamedObject<int>::objectValue 的类型是 int(因为在这个 template instantiation(模板实例化)中 T 是 int),而 int 是 built-in type(内建类型),所以将通过拷贝 no1.objectValue 的每一个二进制位初始化 no2.objectValue。
编译器为 NamedObject<int> 生成的 copy assignment operator(拷贝赋值运算符)本质上也会有同样的行为,但是,通常情况下,只有在结果代码合法而且有一个合理的可理解的巧合时,compiler-generated(编译器生成)的 copy assignment operator(拷贝赋值运算符)才会有我所描述的行为方式。如果这两项检测中的任一项失败了,编译器将拒绝为你的 class(类)生成一个 operator=。
例如,假设 NamedObject 如下定义,nameValue 是一个 reference to a string(引向一个字符串的引用),而 objectValue 是一个 const T:
```
template<class T>
class NamedObject {
public:
// this ctor no longer takes a const name, because nameValue
// is now a reference-to-non-const string. The char* constructor
// is gone, because we must have a string to refer to.
NamedObject(std::string& name, const T& value);
... // as above, assume no
// operator= is declared
private:
std::string& nameValue; // this is now a reference
const T objectValue; // this is now const
};
```
现在,考虑这里会发生什么:
```
std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2); // when I originally wrote this, our
// dog Persephone was about to
// have her second birthday
NamedObject<int> s(oldDog, 36); // the family dog Satch (from my
// childhood) would be 36 if she
// were still alive
p = s; // what should happen to
// the data members in p?
```
assignment(赋值)之前,p.nameValue 和 s.nameValue 都引向 string objects(对象),虽然并非同一个。那个 assignment(赋值)对 p.nameValue 产生了什么影响呢?assignment(赋值)之后,p.nameValue 所引向的 string 是否就是 s.nameValue 所引向的那一个呢,也就是说,reference(引用)本身被改变了?如果是这样,就违反了常规,因为 C++ 并没有提供使一个 reference(引用)引向另一个 objects(对象)的方法。换一种思路,是不是 p.nameValue 所引向的那个 string objects(对象)被改变了,从而影响了其他 objects(对象)—— pointers(指针)或 references(引用)持续指向的那个 string,也就是,赋值中并没有直接涉及到的对象?这是 compiler-generated(编译器生成)的 copy assignment operator(拷贝赋值运算符)应该做的事情吗?
面对这个难题,C++ 拒绝编译代码。如果你希望一个包含 reference member(引用成员)的 class(类)支持 assignment(赋值),你必须自己定义 copy assignment operator(拷贝赋值运算符)。对于含有 const members(const 成员)的 classes(类),编译器会有类似的行为(就像上面那个改变后的 class(类)中的 objectValue)。改变 const members(const 成员)是不合法的,所以编译器隐式生成的 assignment function(赋值函数)无法确定该如何对待它们。最后,如果 base classes(基类)将 copy assignment operator(拷贝赋值运算符)声明为 private,编译器拒绝为从它继承的 derived classes(派生类)生成 implicit copy assignment operators(隐式拷贝赋值运算符)。毕竟,编译器为派生类生成的 copy assignment operator(拷贝赋值运算符)也要处理其 base class parts(基类构件)(参见 Item 12),但如果这样做,它们当然无法调用那些 derived classes(派生类)无权调用的 member functions(成员函数)。
Things to Remember
* 编译器可以隐式生成一个 class(类)的 default constructor(缺省构造函数),copy constructor(拷贝构造函数),copy assignment operator(拷贝赋值运算符)和 destructor(析构函数)。
- Preface(前言)
- Introduction(导言)
- Terminology(术语)
- Item 1: 将 C++ 视为 federation of languages(语言联合体)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 确保 objects(对象)在使用前被初始化
- Item 5: 了解 C++ 为你偷偷地加上和调用了什么函数
- Item 6: 如果你不想使用 compiler-generated functions(编译器生成函数),就明确拒绝
- Item 7: 在 polymorphic base classes(多态基类)中将 destructors(析构函数)声明为 virtual(虚拟)
- Item 8: 防止因为 exceptions(异常)而离开 destructors(析构函数)
- Item 9: 绝不要在 construction(构造)或 destruction(析构)期间调用 virtual functions(虚拟函数)
- Item 10: 让 assignment operators(赋值运算符)返回一个 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中处理 assignment to self(自赋值)
- Item 12: 拷贝一个对象的所有组成部分
- Item 13: 使用对象管理资源
- Item 14: 谨慎考虑资源管理类的拷贝行为
- Item 15: 在资源管理类中准备访问裸资源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一个独立的语句中将 new 出来的对象存入智能指针
- Item 18: 使接口易于正确使用,而难以错误使用
- Item 19: 视类设计为类型设计
- Item 20: 用 pass-by-reference-to-const(传引用给 const)取代 pass-by-value(传值)
- Item 21: 当你必须返回一个对象时不要试图返回一个引用
- Item 22: 将数据成员声明为 private
- Item 23: 用非成员非友元函数取代成员函数
- Item 24: 当类型转换应该用于所有参数时,声明为非成员函数
- Item 25: 考虑支持不抛异常的 swap
- Item 26: 只要有可能就推迟变量定义
- Item 27: 将强制转型减到最少
- Item 28: 避免返回对象内部构件的“句柄”
- Item 29: 争取异常安全(exception-safe)的代码
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之间的编译依赖
- Item 32: 确保 public inheritance 模拟 "is-a"
- Item 33: 避免覆盖(hiding)“通过继承得到的名字”
- Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)
- Item 35: 考虑可选的 virtual functions(虚拟函数)的替代方法
- Item 36: 绝不要重定义一个 inherited non-virtual function(通过继承得到的非虚拟函数)
- Item 37: 绝不要重定义一个函数的 inherited default parameter value(通过继承得到的缺省参数值)
- Item 38: 通过 composition(复合)模拟 "has-a"(有一个)或 "is-implemented-in-terms-of"(是根据……实现的)
- Item 39: 谨慎使用 private inheritance(私有继承)
- Item 40: 谨慎使用 multiple inheritance(多继承)
- Item 41: 理解 implicit interfaces(隐式接口)和 compile-time polymorphism(编译期多态)
- Item 42: 理解 typename 的两个含义
- Item 43: 了解如何访问 templatized base classes(模板化基类)中的名字
- Item 44: 从 templates(模板)中分离出 parameter-independent(参数无关)的代码
- Item 45: 用 member function templates(成员函数模板) 接受 "all compatible types"(“所有兼容类型”)
- Item 46: 需要 type conversions(类型转换)时在 templates(模板)内定义 non-member functions(非成员函数)
- Item 47: 为类型信息使用 traits classes(特征类)
- Item 48: 感受 template metaprogramming(模板元编程)
- Item 49: 了解 new-handler 的行为
- Item 50: 领会何时替换 new 和 delete 才有意义
- Item 51: 编写 new 和 delete 时要遵守惯例
- Item 52: 如果编写了 placement new,就要编写 placement delete
- 附录 A. 超越 Effective C++
- 附录 B. 第二和第三版之间的 Item 映射