文章目录
  1. 1. 2 构造/析构/赋值运算
    1. 1.1. 条款05:了解C++默默编写并调用哪些函数
    2. 1.2. 条款06:若不想使用编译器自动生成的函数,就该明确拒绝
    3. 1.3. 条款07:为多态基类声明virtual析构函数
    4. 1.4. 条款08:别让异常逃离析构函数

2 构造/析构/赋值运算

条款05:了解C++默默编写并调用哪些函数

编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。

定义一个啥也没有的类:

1
class Empty { }

编译器有可能会帮你生成:

1
2
3
4
5
6
7
class Empty {
public:
Empty() {...} //默认构造函数
Empty(const Empty& rhs) {...} //拷贝构造函数
~Empty(){...} //析构函数
Empty& operator=(const Empty& rhs) {...} // copy assignment操作符
}

是否生成这些方法取决于使用的时候是否需要,比如下面这段代码就用到了这些函数

1
2
3
4
Empty e1;		//默认构造函数
//析构函数
Empty e2(e1); //拷贝构造函数
e2 = e1; //copy assignment操作符

条款06:若不想使用编译器自动生成的函数,就该明确拒绝

  • 为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。

正如条款5中提到的,即使你自己没有显示定义,编译器默认会为我们创建一些函数。而在某一些条件下,这些函数所带来的行为并不是你想要的。

那么这个时候你就应该把你想要隐藏的方法声明为private并不提供任何的定义,这样任何尝试去使用的人都会收到一个链接错误。

什么的场景下可能需要使用这种方法?

  • private 构造函数
    当你希望用户不能够随意的自己去创建实例而是通过某种方法获得一个实例的时候。比如说单例模式。
  • private 析构函数
    当你希望一个实例的生命周期由另一个类来管理的时候使用。比如说,你需要你的实例使用引用计数,如果不是0则不能够析构。这个时候你就需要提供比如说:Acquire,Release的方法。我觉得如果只是reference count的话,利用shared_ptr也能够实现类似的功能。
    参考链接:Private Destructors
  • private 拷贝构造函数和copy assignment 操作符
    当你希望一个对象是不可拷贝的时候。
    Example:
    1
    2
    3
    4
    5
    6
    Class Uncopyable {
    ...
    private:
    Uncopyable(const Uncopyable&);
    ~Uncopyable& operator=(const Uncopyable&);
    }

条款07:为多态基类声明virtual析构函数

  • polymoriphic(带多态性质的)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数
  • Classes的设计目的如果不是作为base classes使用,或者不是为了具备多态性(polymorphically),就不该声明virtual析构函数。

为什么带有多态性质的基类应该声明一个virtual析构函数?

带有多态性质的基类意味着一个基类的指针可能存放着子类的实例。在这种情况下如果析构函数没有声明为virtual的话,delete一个基类指针不能够正确的调用到子类的析构函数。这就意味着会造成资源的泄露。

但是如果一个类设计的时候没有想让别的类继承,如果另一个类继承了,就可能会出现资源的泄露。因为C++在语言层面没有提供类似于C# seal的语法,所以没有办法杜绝这种情况。如果发现一个类的析构函数不是virtual的那么就不应该去继承它。

反过来看就更简单了,如果一个类设计的目的不是作为基类使用,也就意味着它的指针不可能指向一个子类的对象。那么声明成virtual的就没有太大必要,反而会因为virtual而增长了这个对象的内存大小。

条款08:别让异常逃离析构函数

  • 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或者结束程序。
  • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

为什么析构函数中抛出异常不好?

  1. 析构函数被调用的地方不好控制,而且不好对其进行处理。因为任何对象都有析构函数,你不可能在任何地方对异常进行捕捉。(自己补充的)
  2. 如果析构函数中所抛出异常未处理会导致未定义行为。

所以如果在析构函数中有可能执行到一些会抛异常的方法,需要对其进行捕捉。如果你认为异常可以忍受则默默吞下,记下log,否则记下log然后直接结束程序。

有什么更好的办法?

如果在析构函数里需要进行某个可能抛出异常的方法,那么就应该提供一个普通方法允许用户自己调用。这样子用户就有机会自己对其进行处理。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class DBConn {
public:
~DBConn() {
db.close(); //bad, may throw exception
}
private:
DBConnection db;
}

class DBConn {
public:
void close() {
db.close();
closed = true;
}
~DBConn() {
if (!closed) {
try {
db.close(); //good, catch exception and provide normal method to close
}
catch (...) {
//log
}
}
}
private:
DBConnection db;
}
文章目录
  1. 1. 2 构造/析构/赋值运算
    1. 1.1. 条款05:了解C++默默编写并调用哪些函数
    2. 1.2. 条款06:若不想使用编译器自动生成的函数,就该明确拒绝
    3. 1.3. 条款07:为多态基类声明virtual析构函数
    4. 1.4. 条款08:别让异常逃离析构函数