本篇主要针对构造、赋值以及析构函数实现中需要注意的事项,从虚函数、继承角度看待这些函数需要注意的事项
小结
- 如果类内部存在资源,则不要使用默认生成的,默认生成的依然可能会报错
- 构造、拷贝构造:如果父类没有默认构造,则需要在初始化列表显示写出,且不要在里面调用虚函数,最后不要将异常抛出
- 赋值:传入
const reference,返回reference to *this,内部处理自我赋值,当一个函数操作多个对象时,需要保证多个对象是同一个对象时不会出错;另外如果有继承,需要保证父类元素也被赋值
条款 05:了解cpp默默编写并调用哪些函数
- 如果不提供实现,则添加默认构造、拷贝构造、赋值操作以及析构函数
- 尽量在初始化列表中初始化所有数据成员,特别是成员有const或者引用的
- 生成的默认构造函数,1.调用基类构造;2.在初始化列表对成员调用默认构造。这两步都可能出错,比如第一步中基类的构造函数在private中,派生类无权访问
条款 06:若不想使用编译器自动生成的函数,就该明确拒绝
- 如果想去掉比如拷贝构造,直接 =delete
条款 07:为多态基类声明 virtual析构函数
- 纯虚析构函数必须提供定义
- 如果class带有任何virtual函数,它就应该拥有一个virtual析构函数
条款 08:别让异常逃离析构函数
- 将异常捕获(次优)
- 提供接口,让用户调用可能报错的api,当然在析构中也得判断是否真正被调用
条款 09:绝不在构造和析构过程中调用 virtual函数
- 当执行基类的构造、析构函数时,此时类是基类,而不是最终的派生类,所以在里面调用的虚函数其实是调用的基函数中的实现,如果是纯虚,则报错
条款 10:令operator=返回一个reference to *this
条款 11:在 operator= 中处理“自我赋值“
- 确保一个函数操作多个对象时,多个对象都是同一个对象时的正确性
- 使用if直接判断,然后再赋值删除(存在问题)
- swap方法(推荐),借助局部变量会释放资源,swap交换两个对象的数据成员
条款 12:复制对象时勿忘其每一个成分
- 不要忘记继承而来的数据成员,同时需要在拷贝、赋值构造中调用相应的基类函数来初始化值
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;
}