Effective C++ 笔记(2)
第二部分: 构造/析构/赋值运算
条款05: 了解C++默默编写并调用哪些函数
对于一个类,如果自己没声明,那么编译器会默认生成一个copy构造函数、一个copy assignment操作符和一个析构函数。此外如果你没有声明任何构造函数,编译器还会声明一个default构造函数。
所有这些函数都是public并且inline的。
因此,如果你写下
class Empty {};
这就好像你写下这样的代码
class Empty {
Empty() { ... } //default构造函数
Empty(const Empty& rhs){ ... } //copy构造函数
~Empty(){ ... } //析构函数
Empty& operator=(const Empty& rhs){ ... } //copy assignment操作符
}
请记住
编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符以及析构函数。
条款06: 若不想使用编译器自动生成的函数, 就该明确拒绝
如果不想编译器支持某个类的copy构造函数或者copy assignment操作符, 可以自己创建它们的空函数,然后声明为private(一般来说,因为private声明,它们不能被调用,一旦menber函数和friend函数调用,则会生成连接错误阻止它们的行为)
不想让menber函数和friend函数的调用到连接阶段才报错?可以创建一个Uncopyable基类,继承它即可。
class Uncopyable {
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator= (const Uncopyable&);
}
请记住
- 为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。
条款07: 为多态基类声明virtual析构函数
请记住
- 带多态性质的基类应该声明一个虚析构函数。如果类带有任何虚函数,它就应该拥有一个虚析构函数。
- 一个类的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明虚析构函数。
条款08: 别让异常逃离析构函数
请记住
- 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常, 然后吞下它们(不传播)或者结束程序。
- 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
条款09: 绝不在构造和析构过程中调用virtual函数
请记住
- 在构造和析构期间不要调用virtual函数,因为这类调用从不下降到派生类
struct Super {
Super() { test(); }
virtual ~Super() { test(); }
virtual void test() { std::cerr << "super" << std::endl; }
};
struct Derived : Super {
void test() override { std::cerr << "derived" << std::endl; }
};
int main() {
Super *p = new Derived;
p->test();
delete p;
}
输出:
super
derived
super
条款10: 令operator=返回一个reference to *this
为了让自定义类型实现如下的连锁赋值
int x, y, z;
x = y = z = 15;
class的赋值操作符必须返回一个reference指向操作符的左侧实参
class Widget{
public:
Widget& operator=(const Widget& rhs) { //返回类型是个reference
...
return *this //返回左侧对象
}
}
这份协议被所有内置类型和标准程序库所提供的类型共同遵守。
请记住:
- 令赋值操作符返回一个reference to *this
条款11: 在operator=中处理自我赋值
请记住:
- 确保当对象自我赋值时=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
- 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
条款12: 复制对象时勿忘其每一个成分
为派生类撰写copying函数的重责大任,必须小心地复制其base class 成分。应该让派生类的copying构造函数调用相应的基类函数:
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
: Customer(rhs),
priority(rhs.priority) {
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs) {
logCall("PriorityCustomer copy assignment operator");
Customer::operator=(rhs);
priority = rhs.priority;
return *this;
}
当编写一个copying函数,请确保(1)复制所有local成员变量,(2)调用基类内的适当copying函数。
请记住:
- Copying函数应该确保复制"对象内的所有成员变量"以及"所有base class成分"
- 不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数,并由两个coping函数共同调用。(不应让copy assignment操作符调用copy构造函数,也不该让copy构造函数调用copy assignment操作符)