条款5: c++默默编写并调用那些函数
- 对于一个空类, 编译器默认会生成default构造函数,析构函数,copy构造函数,copy assignment操作符,
并且这些都是public且inline的; 只有当这些函数被需要(即代码中有调用)时才会生成;
1
2
3
4
5
6
7class Empty{
public:
Empty() {} // 如果类里面已经自定义了一个构造函数, 则编译器不会生成此构造函数
Empty(const Empty& rhs) {...} // 单纯的将每一个non-static变量拷贝到目标对象
~Empty() {} // 析构函数, 编译器默认的创建的是non-virtual,除非有子类, 并且子类的析构函数是virtual的
Empty& operator=(const Empty& rhs) {...} // 同copy构造函数
};如果一个类内含reference变量 或 内含const变量,编译器默认生成的copy构造函数和copy assignment操作符
将不知所措, 所以此时应该自己实现;- 如果base class将copy assignment操作符声明为private, 编译器将拒绝为derived class 生成一个copy
assignment 操作符, 由于子类无权调用子类的函数, 编译器无能为力, 所以就….
条款6: 若不想使用编译器自动生成的版本,就明确拒绝
将copy 构造函数 和 copy assignment操作符声明为private且不予实现;
1
2
3
4
5
6
7
8
9class HomeForSale{
public:
...
private:
HomeForSale(const HomeForSale&);
HomeForSale& operator=(const HomeForSale&);
};
// 定义上述代码后, 当客户企图拷贝时, 编译器会阻挠;
// 但是你不慎在member函数或friend函数之内调,链接器可能会抱怨.对于想杜绝在member函数或friend函数内调用的情况,可以声明如下类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Uncopyable {
protected:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
// 然后在需要阻止的时候继承此类就可以了
class HomeForSale: private Uncopyable {
...
};
// 由于在编译器生成版的函数中会尝试调用子类的接口, 但是他们是private
// 的, 所以就会被编译器拒绝而导致编译错误.
条款7: 为多态基类声明virtual析构函数
- c++明确指出,当derived class 对象经由一个base class指针被删除, 而该base
class带有一个non-virtual析构函数时, 其结果是未定义的, 其通常结果是对象
的derived成分没有被销毁; - 如果一个class不企图被当做一个bas class, 令其析构函数为virtual往往是一个
馊主意; 由于声明虚函数需要占用额外空间(vptr); - 只有当class内含至少一个virtual函数,才为它声明virtual析构函数;
- 标准的string不含任何virtual函数;
- 在你需要一个pure class, 但是你又没有pure virtual 函数, 你可以声明一个pure virtual析构函数,
并且提供定义, 因为编译器会在AWOW的子类的析构函数中创建一个对~AWOW的调用动作, 如果不定义,
链接器会报错;1
2
3
4
5class AWOW{
public:
virtual ~AWOW()=0;
};
AWOW::~AWOW(){}
条款8: 别让异常逃离析构函数
- c++标准不禁止析构函数抛出异常, 但是不鼓励;
- 析构函数应该捕捉任何异常, 然后处理掉或结束程序;
- 如果客户需要对某个操作函数运行期间抛出的异常作反应, 应该用一个普通函数, 而不是在析构函数中.
条款9: 绝不在构造函数和析构函数中调用 virtual函数
如下代码:1
2
3
4
5
6
7
8
9
10
11
12
13class Transaction{
public:
Transaction() { logTransaction(); }
virtual void logTransaction() const=0; // 做出一份因类型不同而不同的日志记录
};
class BugTransaction: public Transaction{
public:
virtual void logTransaction() const;
};
class SellTransaction: public Transaction {
public:
virtual void logTransaction() const;
};
- 当derived class构造函数被调用时, base class一定会更早被调用; 而 derived
class构造函数调用virtual函数时, 调用的是base class版本; 在base class
构造期间virtual函数绝不会下降到derived class阶层; - 当 derived class 析构函数开始执行, 对象内的 derived class 成员变量便呈现未定义值,
所以c++视他们仿佛不存在. 进入 base class 析构函数对象就成为一个 base class 对象; - 解决的办法: 将 logTransaction 改为 non-virtual, 然后要求derived class 构造函数传递必要信息给
Transaction 构造函数, 如下:1
2
3
4
5class Transaction {
public:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo) const;
}
条款10: 令 operator= 返回一个 reference to *this
1 | int x, y, z; |
为了实现”连锁赋值”, 赋值操作符必须返回一个 reference 直线操作符的左侧实参, 如下:
1 | Widget& operator=(const Widget& rhs) { |
同样适用于+=, -=, *=等等.
条款11: 在operator= 中处理 “自我赋值”
- 最初版的
opeator=
:
1 | class Bitmap { |
- 如果rhs 和 this 是同一个对象的话, delete 则会产生错误; 针对这种情况应该添加
证同测试
:
1 | Widget& Widget::opeator=(const Widget& rhs) { |
上述虽然实现了
自我赋值安全性
, 但是缺乏异常安全性
, 如果delete pb
后,new
失败后, 岂不是this
的pb指向了一个无效指针, 我们应该在赋值pb
所指东西之前别删除pb
:1
2
3
4
5
6
7Widget& Widget::opeator=(const Widget& rhs) {
// if (this == &rhs) return *this; // 不是绝对必要, 但是关心效率的话可以加上
Bitmap* temp = pb;
pb = new Bitmap(*rhs.pb);
delete temp;
return *this;
}替代方案:
copy and swap
技术:1
2
3
4
5
6
7
8
9class Widget{
void swap(Widget& rhs); // 交换数据
};
Widget& Widget::opeator=(const Widget& rhs) {
Widget temp(rhs);
swap(temp);
return (*this);
}
note:1. 确保当对象自我赋值时, operator =仍然行为良好; 2. 确定任何函数操作多个对象时,
并且其中多个对象是同一个对象时, 其行为仍然正确.
条款12: 复制对象时勿忘每一个成分
设计良好的面向对象系统(OO-System)会将对象的内部封装起来,只留俩个函数负责对象拷贝(复制),
那便是copy构造函数和copy assignment操作符, 称之为copying函数.
note:
- copying 函数应该确保复制”对象内的所有成员变量” 及 “所有base class 成分”
- 不要尝试以某个copying 函数实现另一个copying 函数. 应该将共同机能放进第三个函数中,
并由两个coping 函数共同调用.