构造和析构一方面是对象的诞生和终结;另一方面,它们也意味着资源的开辟和归还。
条款05:了解C++默默编写并调用了哪些函数
编译器会主动为你编写的任何类声明一个拷贝构造函数、拷贝复制操作符和一个析构函数,同时如果你没有声明任何构造函数,编译器也会为你声明一个default版本的拷贝构造函数,这些函数都是public
且inline
的。注意,上边说的是声明,只有当这些函数有调用需求的时候,编译器才会帮你去实现它们。
C++ 中的空类并不是真正意义上的空类,编译器会为它预留以下内容:
class Empty {
public:
Empty() { ... } // 默认构造函数(没有任何构造函数时)
Empty(const Empty&) { ... } // 拷贝构造函数
Empty(Empty&&) { ... } // 移动构造函数 (since C++11)
~Empty() { ... } // 析构函数(non-virtual)
Empty& operator=(const Empty&) { ... } // 拷贝赋值运算符
Empty& operator=(Empty&&) { ... } // 移动赋值运算符 (since C++11)
};
唯有当这些函数被调用时,它们才会真正被编译器创建出来,下面代码将造成上述每一个函数被创建:
mpty e1; // 默认构造函数 & 析构函数
Empty e2(e1); // 拷贝构造函数
Empty e3 = std::move(e2); // 移动构造函数 (since C++11)
e2 = e1; // 拷贝赋值运算符
e3 = std::move(e1); // 移动赋值运算符 (since C++11)
需要注意的是,拷贝赋值运算符只有在允许存在时才会自动创建,比如以下情况:
class NamedObject {
private:
std::string& nameValue;
};
在该类中,我们有一个string引用类型,然而引用无法指向不同对象,因此编译器会拒绝为该类创建一个默认的拷贝赋值运算符。
除此之外,以下情形也会导致拷贝赋值运算符不会自动创建:
- 类中含有const成员。
- 基类中含有private的拷贝赋值运算符。
注:编译器替你实现的函数可能在类内引用、类内指针、有const
成员以及类型有虚属性的情形下会出问题。
- 对于拷贝构造函数,你要考虑到类内成员有没有深拷贝的需求,如果有的话就需要自己编写拷贝构造函数/操作符,而不是把这件事情交给编译器来做。
- 对于拷贝构造函数,如果类内有引用成员或
const
成员,你需要自己定义拷贝行为,因为编译器替你实现的拷贝行为在上述两个场景很有可能是有问题的。 - 对于析构函数,如果该类有多态需求,请主动将析构函数声明为
virtual
,具体请看条款07 。
除了这些特殊的场景以外,如果不是极其简单的类型,请自己编写构造、析构、拷贝构造和赋值操作符、移动构造和赋值操作符(C++11、如有必要)这六个函数。