拷贝赋值运算符行为: 类值和类指针
2023年12月30日 2023年12月31日
与拷贝赋值的语义有关
-
行为像值的类
类值
使用拷贝赋值运算符时, 副本和原对象相互独立
如string
-
行为像指针的类
类指针
使用拷贝赋值运算符赋值后, 副本和原对象使用相同的底层数据
如shared_ptr和initializer_list
-
其他
如unique_ptr
如果一个类需要自定义析构函数, 几乎可以肯定它也需要自定义拷贝赋值运算符和拷贝构造函数
类值: 拷贝赋值运算符, 拷贝构造函数与析构函数
1class HasPtr 2{ 3public: 4 HasPtr(const string &s = string()) : ps(new string(s)), i(0) { } 5 HasPtr(const HasPtr &p) : ps(new string(*p.ps), i(p.i)) { } 6 HasPtr &operator=(const HasPtr &); 7 ~HasPtr() { delete ps; } 8 9private: 10 string *ps; 11 int i; 12}; 13 14HasPtr &HasPtr::operator=(const HasPtr &rhs) 15{ 16 auto newp = new string(*rhs.ps); 17 delete ps; 18 ps = newp; 19 i = rhs.i; 20 21 return *this; 22}
拷贝赋值运算符
- 通常组合了析构函数和构造函数的操作:
- 销毁左侧运算对象
- 拷贝右侧运算对象
- 销毁左侧运算对象
- 要保证将对象赋予它自身时, 能正确工作
安全性说明
-
申请动态内存可能会失败: 需保证该异常不会影响到原对象
-
右侧运算对象和
*this
可能为同一个对象: 读取完毕后销毁左侧运算对象
1HasPtr &HasPtr::operator=(const HasPtr &rhs) 2{ 3 auto newp = new string(*rhs.ps); 4 5 delete ps; 6 ps = newp; 7 8 i = rhs.i; 9 10 return *this; 11}
类指针: 拷贝赋值运算符, 拷贝构造函数与析构函数
可以帮助理解shared_ptr
1class HasPtr 2{ 3public: 4 HasPtr(const string &s = string()) : ps(new string(s)), i(0), use(new size_t(1)) { } 5 HasPtr(const HasPtr &p) : ps(new string(*p.ps), i(p.i)), use(p.use) { ++*use; } 6 HasPtr &operator=(const HasPtr &); 7 ~HasPtr(); 8 9private: 10 string *ps; 11 int i; 12 size_t *use; 13}; 14 15HasPtr::~HasPtr() 16{ 17 if (--*use == 0) 18 { 19 delete ps; 20 delete use; 21 } 22} 23 24HasPtr &HasPtr::operator=(const HasPtr &rhs) 25{ 26 ++*rhs.use; 27 28 if (--*use == 0) 29 { 30 delete ps; 31 delete use; 32 } 33 34 ps = rhs.ps; 35 use = rhs.use; 36 i = rhs.i; 37 38 return *this; 39} 40 41int main() 42{ 43 HasPtr p1("Hiya!"); 44 HasPtr p2(p1); 45 HasPtr p3(p1); 46 return 0; 47}
引用计数的实现
将计数器保存在动态内存中
基类与派生类共享静态数据成员
拷贝赋值运算符
自赋值处理没有问题, 一次递增一次递减抵消了