2.构造析构赋值篇

51 阅读3分钟

本篇主要针对构造、赋值以及析构函数实现中需要注意的事项,从虚函数、继承角度看待这些函数需要注意的事项

小结

  1. 如果类内部存在资源,则不要使用默认生成的,默认生成的依然可能会报错
  2. 构造、拷贝构造:如果父类没有默认构造,则需要在初始化列表显示写出,且不要在里面调用虚函数,最后不要将异常抛出
  3. 赋值:传入const reference,返回reference to *this,内部处理自我赋值,当一个函数操作多个对象时,需要保证多个对象是同一个对象时不会出错;另外如果有继承,需要保证父类元素也被赋值

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

  1. 如果不提供实现,则添加默认构造、拷贝构造、赋值操作以及析构函数
  2. 尽量在初始化列表中初始化所有数据成员,特别是成员有const或者引用的
  3. 生成的默认构造函数,1.调用基类构造;2.在初始化列表对成员调用默认构造。这两步都可能出错,比如第一步中基类的构造函数在private中,派生类无权访问

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

  1. 如果想去掉比如拷贝构造,直接 =delete

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

  1. 纯虚析构函数必须提供定义
  2. 如果class带有任何virtual函数,它就应该拥有一个virtual析构函数

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

  1. 将异常捕获(次优)

image.png

  1. 提供接口,让用户调用可能报错的api,当然在析构中也得判断是否真正被调用

image.png

条款 09:绝不在构造和析构过程中调用 virtual函数

  1. 当执行基类的构造、析构函数时,此时类是基类,而不是最终的派生类,所以在里面调用的虚函数其实是调用的基函数中的实现,如果是纯虚,则报错

条款 10:令operator=返回一个reference to *this

条款 11:在 operator= 中处理“自我赋值“

  1. 确保一个函数操作多个对象时,多个对象都是同一个对象时的正确性
  2. 使用if直接判断,然后再赋值删除(存在问题) image.png
  3. swap方法(推荐),借助局部变量会释放资源,swap交换两个对象的数据成员 image.png

条款 12:复制对象时勿忘其每一个成分

  1. 不要忘记继承而来的数据成员,同时需要在拷贝、赋值构造中调用相应的基类函数来初始化值
 class Derived : public Base {
public:
    // 拷贝构造函数
    Derived(const Derived& other)
        : Base(other),              // 调用基类的拷贝构造函数
          derived_member(other.derived_member) {  // 拷贝派生类成员
        // 可选的额外逻辑
    }
    // 赋值运算符
    Derived& operator=(const Derived& other) {
        if (this != &other) {              // 检查自赋值
            Base::operator=(other);        // 调用基类的赋值运算符
            derived_member = other.derived_member;  // 赋值派生类成员
        }
        return *this;
    }

private:
    Type derived_member;
};

2. 在拷贝赋值运算符中,不要对当前已构造的对象直接调用拷贝构造函数,operator=中swap是创建临时变量,所以是合理的

// ❌ 错误示例
Derived& operator=(const Derived& other) {
    if (this != &other) {
        *this = Derived(other);  // 等价于在已构造对象上调用拷贝构造(荒谬)
    }
    return *this;
}