EffectiveCpp0x2
Effective C+
[toc]
Constructors,Destructors,and Assignment Operators
05. Know what functions C++ silently writes and calls
- C++自动为类声明
copy构造函数
,copy assignment操作符
和析构函数
。如果没有声明构造函数,编译器会生成default构造函数
。 这些函数都是public
且inline
的。
1 |
|
06. Explicitly disallow the use of compiler-generated functions you do not want
直接
delete
掉。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Entity {
private:
// 禁用默认构造函数
Entity() = delete;
// 禁用拷贝构造函数
Entity(const Entity&) = delete;
// 禁用拷贝赋值运算符
Entity& operator=(const Entity&) = delete;
// 允许声明析构函数,但不提供实现(可选)
~Entity();
public:
// 其他成员和方法
};
07. Declare destructors virtual in polymorphic base classes
- 因为C++明白指出,当
derived class对象
经由一个base class指针
被删除,而该base class
带着一个non-virtual析构函数
,其结果未有定义
——实际执行时通常发生的是对象的derived成分没被销毁
。 vptr(virtual table pointer)
指针指出运行期哪一个virtual函数
应该被调用。- 不要继承
non-virtual类
,如std::string
、容器类
等。他们没有virtual虚构函数
。 - 析构调用顺序:最深层派生(most derived)的那个class其析构函数最先被调用,然后是每个base class的析构函数被调用。
请记住
polymorphic (带多态性质的)
base classes 应该声明-一个virtual 析构函数
。如果class带有任何virtual函数
,它就应该拥有一个virtual析构函数
。多态
即通过base class接口
来处理derived class对象
。- Classes 的设计目的如果不是作为base classes 使用,或
不是为了具备多态性(polymorphically)
,就不该声明vitual析构函数
。
08. Prevent exceptions from leaving destructors
- C++并不禁止析构函数吐出异常,但它不鼓励你这样做。
- 在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为:在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为
GPT3.5:如何理解在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为
- 异常链:当一个异常抛出并且未被捕获时,程序会终止当前的执行,并且尝试在调用栈中查找匹配的
catch
块。如果在这个过程中,另一个异常被抛出,而且两个异常都没有被捕获,那么程序的行为将变得不明确。这可能导致未定义的行为或者程序终止。- 资源泄漏:如果一个异常导致了资源泄漏(比如内存泄漏、文件句柄泄漏等),而另一个异常又同时发生,那么程序可能无法正常处理这些异常,进而导致不明确的行为。
- 数据破坏:如果一个异常导致了数据的破坏,而另一个异常又同时发生,那么程序可能无法正确处理这些异常,从而导致数据损坏或者不一致性。
在如关闭文件,关闭数据库的连接,可能抛出异常,我们有两种解决方案:
如果close 抛出异常就结束程序。通常通过调用abort 完成:
1
2
3
4
5
6
7
8
9
10
11~Destruction() {
try {
// 尝试关闭资源。
close();
} catch(...) { // 捕获所有类型的异常
// 如果关闭操作失败,则记录日志
log();
// 然后终止程序
std::abort();
}
}
吞下因调用close 而发生的异常:
1
2
3
4
5
6
7
8
9~Destruction() {
try {
// 尝试关闭资源。
close();
} catch(...) {
// 如果关闭操作失败,则记录日志
log();
}
}较佳策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Destruction {
public:
~Destruction() {
try {
// 尝试关闭资源。
close();
} catch(...) {
// 如果关闭操作失败,记录日志,吞下异常.
log();
}
}
void close() {
// 提供客户一个close()函数
}
void log() {
// 实现日志记录逻辑
}
};
请记住
- 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
- 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class 应该提供一个普通函数(而非在析构函数中)执行该操作。
09. Never call virtual functions during construction or destruction
- 有点难懂,百度
//TODO
10. Have assignment operators return a reference to *this
- 赋值采用右结合律。要实现连锁赋值,要返回一个
reference
指向操作符的左侧实参。
1 | class Entity { |
请记住
- 令赋值(assignment) 操作符返回一个reference to *this。
11. handle assignment to self in operator=
- 证同测试(identity test)
1 |
|
- 复制前先别删除
1 | Entity& Entity::operator=(const Entity& rhs) |
- copy and swap
1 | Entity& Entity::operator=(const Entity& rhs) { |
- 确保当对象自我赋值时operator= 有良好行为。其中技术包括比较”来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap 。
- 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
12. Copy all parts of an object.
1) 复制所有local 成员变量。
2) 调用所有base classes 内的适当的copying 函数。
请记住
- Copying 函数应该确保复制”对象内的所有成员变量”及“所有base class 成分”。
- 不要尝试以某个copying 函数实现另一个copying 函数。应该将共同机能放进第三个函数中,并由两个copying 函数共同调用。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Randolfluo's blog!