若类中有资源在构造函数中创建,并在析构函数中释放,此时需要显式定义拷贝构造、赋值,析构等操作,若在程序没有显示声明并定义时,会被隐式生成,对于不包含联合体的类,隐式生成的拷贝构造函数和赋值运算在执行时,会按成员对象依次复制,隐式生成的析构函数为空
如下面的类T
管理资源int*
class T{
public:
T(int i){
pi= new int(i);
// pi= std::make_shared<int>(i);
}
private:
int* pi;
// std::shared_ptr<int> pi;
};
1. 显式定义拷贝构造函数的情况
T(const T& t){
// this->pi= new int(*t.pi);
this->pi= new int; // 申请内存,并复制对象
*this->pi= *t.pi;
}
2. 显式重载赋值运算符的情况
实现赋值运算时,应考虑的问题:
- 自检查
防止自赋值 - 删除自身资源
若在new
时抛出异常,则会无法恢复原状态,可用临时变量或后面介绍的copy-and-swap解决 - 复制资源
代码冗余,和拷贝构造中做一样的操作
T& operator= (const T& t){
if(this!=&t){
this->pi= new int;
*this->pi= *t.pi;
return *this;
}
}
3. 显式定义析构函数
~T(){
delete pi; // 释放资源
}
不可复制的资源
如文件句柄和互斥量等是不可复制的,此时拷贝构造函数和赋值运算需要特殊处理
- 定义在
private
中private: T(const T& t); T& operator=(const T& t);
- 用C++11中的
delete
实现T(const T& t) = delete; T& operator=(const T& t) = delete;
C++11五法则
C++11中引入了右值引用,多了移动语义,所以相应地新增了移动构造和移动赋值运算
T(T&&) noexcept= default;
T& operator=(T&&)noexcept= default;
建议:
- 尽量不要在一个类中管理多个资源,否则日后会知道什么是痛苦
- 若要显式定义析构函数、复制构造函数或赋值运算符中的一个,则也应显式定义另外两个
- 大多数情况下都没必要自己实现管理资源的类,
std
中基本上都有实现,只要避免原始指针,”五法则”基本上也就用不到
copy-and-swap
任何资源管理类,都需要遵循三法则,其中,拷贝构造和析构函数的实现相对简单,赋值运算符重载会复杂许多
copy-and-swap是实现赋值运算符重载的完美解决方案,既能避免代码冗余,又可以提供强异常安全保证
大致思路:先用拷贝构造创建一个副本,再利用swap交换数据成员,在作用域结束时,副本会自动调用析构函数
在实现赋值运算时,使用copy-and-swap解决上述问题
friend void swap(T& src, T& dest){
std::swap(src.str, src.str); // <algorithm>
}
T& operator=(T t){
swap(*this, t);
return *this;
}
标签:法则,int,三五,C++,swap,显式,const,pi,赋值
From: https://www.cnblogs.com/sgqmax/p/18518520