Effective C+

[toc]

Constructors,Destructors,and Assignment Operators

05. Know what functions C++ silently writes and calls

  • C++自动为类声明copy构造函数copy assignment操作符析构函数。如果没有声明构造函数,编译器会生成default构造函数。 这些函数都是publicinline的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<iostream>

class Entity
{
public:
Entity() {} // Default constructor
~Entity() {} // Destructor
Entity(const Entity& rhs) {} // Copy constructor
Entity operator=(const Entity& rhs) { return *this;} // Copy assignment operator
};

int main()
{
Entity e;
}

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
    17
    class 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:如何理解在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为

  1. 异常链:当一个异常抛出并且未被捕获时,程序会终止当前的执行,并且尝试在调用栈中查找匹配的catch块。如果在这个过程中,另一个异常被抛出,而且两个异常都没有被捕获,那么程序的行为将变得不明确。这可能导致未定义的行为或者程序终止。
  2. 资源泄漏:如果一个异常导致了资源泄漏(比如内存泄漏、文件句柄泄漏等),而另一个异常又同时发生,那么程序可能无法正常处理这些异常,进而导致不明确的行为。
  3. 数据破坏:如果一个异常导致了数据的破坏,而另一个异常又同时发生,那么程序可能无法正确处理这些异常,从而导致数据损坏或者不一致性。
  • 在如关闭文件,关闭数据库的连接,可能抛出异常,我们有两种解决方案:

    1. 如果close 抛出异常就结束程序。通常通过调用abort 完成:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      ~Destruction() {
      try {
      // 尝试关闭资源。
      close();
      } catch(...) { // 捕获所有类型的异常
      // 如果关闭操作失败,则记录日志
      log();
      // 然后终止程序
      std::abort();
      }
      }
  1. 吞下因调用close 而发生的异常:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ~Destruction() {
    try {
    // 尝试关闭资源。
    close();
    } catch(...) {
    // 如果关闭操作失败,则记录日志
    log();
    }
    }
  2. 较佳策略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class 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
2
3
4
5
6
7
8
class Entity {
// 重载赋值操作符
Entity& operator=(const Entity& e) {
// 返回*this允许链式赋值
return *this;
}
}

请记住

  • 令赋值(assignment) 操作符返回一个reference to *this。

11. handle assignment to self in operator=

  • 证同测试(identity test)
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<iostream>

class Entity
{
public:
Entity(int a,int b)
{
value = new int[2];
value[0] = a;
value[1] = b;
std::cout<< "Construction!!!" << std::endl;
}
~Entity()
{
delete []value;
std::cout<< "Destruction!!!" << std::endl;
}
Entity& operator=(const Entity& rhs);
void print();
private:
int* value;
};

Entity& Entity::operator=(const Entity& rhs)
{
if (this != &rhs) {
value[0] = rhs.value[0];
value[1] = rhs.value[1];
}
return *this;
}

void Entity::print()
{
std::cout<< this->value[0] << " " <<this->value[1] << std::endl;

}

int main()
{
Entity e(123, 456);
Entity e1(789, 123);
e = e1;
e.print();

}
  • 复制前先别删除
1
2
3
4
5
6
7
8
9
10
11
12
Entity& Entity::operator=(const Entity& rhs)        
{
//遵循了异常安全的原则,特别是提供了强异常安全保证。
// 异常安全:如果在new分配内存时发生异常,当前对象的状态不会改变,因为它仍然保持着对原始内存的控制。新内存的分配和初始化发生在任何旧资源被释放之前。
int* tmp = new int[2];
tmp[0] = rhs.value[0];
tmp[1] = rhs.value[1];
delete []value;
value = tmp;
return *this;
}
//也许不是最高效的方法,但是行得通
  • copy and swap
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
Entity& Entity::operator=(const Entity& rhs) {
// 创建一个临时的Entity对象,并用rhs的值初始化它
Entity tmp(rhs.value[0], rhs.value[1]);

// 调用swap方法交换临时对象和当前对象的值
swap(tmp);

// 返回当前对象的引用,支持链式赋值
return *this;
}

void Entity::swap(Entity &rhs) {
// 创建两个临时变量,用于暂存值
int a, b;
// 将rhs对象的值赋给临时变量a和b
a = rhs.value[0];
b = rhs.value[1];

// 交换rhs对象和当前对象的值
rhs.value[0] = this->value[0];
rhs.value[1] = this->value[1];
this->value[0] = a;
this->value[1] = b;
}

  • 确保当对象自我赋值时operator= 有良好行为。其中技术包括比较”来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap 。
  • 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

12. Copy all parts of an object.

1) 复制所有local 成员变量。
2) 调用所有base classes 内的适当的copying 函数。

请记住

  • Copying 函数应该确保复制”对象内的所有成员变量”及“所有base class 成分”。
  • 不要尝试以某个copying 函数实现另一个copying 函数。应该将共同机能放进第三个函数中,并由两个copying 函数共同调用。