13.2.0拷贝控制和资源管理
管理类外的资源的类必须定义拷贝控制成员。需要确定此类型对象的拷贝语义,有让类看起来像一个值或者一个指针
- 类的行为像一个值,意味着它应该也有自己的状态。拷贝一个像值的对象时,副本和原对象是完全独立的。改变副本不会对原对象有任何影响
- 类的行为像指针,拷贝一个这种类的对象时,副本和原对象使用相同的底层数据。改变副本也会改变原对象
如:
string类的行为像一个值
Shared_ptr类的行为像一个指针
IO类型和unique_ptr不允许拷贝或赋值,他们的行为既不像值也不像指针
13.2.1行为像值的类
| HasPtr(const HasPtr &p): Ps(new std::string(*p.ps)),i(p.i){} Auto newp = rhs; HasPtr& HasPtr::operator=(const HasPtr &rhs) {delete ps; Ps = new string(*(rhs.ps)); I=rhs.i; Return *this;} |
1. 行为像值的类 1)每个对象都应该拥有一份自己的拷贝 如:类中有一个string的指针ps, 在拷贝构造函数需要完成string的拷贝,而不是拷贝指针 在析构函数来释放string 在拷贝赋值运算符来释放当前的string,并从右侧运算对象拷贝string 2. 类值拷贝赋值运算符 1)组合了析构函数和构造函数的操作 关键概念:赋值运算符 1)如果将一个对象赋予它的自身,赋值运算符必须能正确工作 2)大多数赋值运算符组合了析构函数和拷贝构造函数的工作 好的模式写法是:先将右侧运算对象拷贝到一个局部临时对象中。再销毁左侧运算符对象 Note:特别是对象赋予它的自身,如果先销毁左侧对象,那么右侧对象也跟着销毁,所以一定先要保存右侧运算对象 |
13.2.2定义行为像指针的类
| class HasPtr { public: HasPtr(const string &s = string()) : pss(new string(s)), i(0),use(new size_t(1)) {} HasPtr(const HasPtr& hp):pss(hp.pss),i(hp.i),use(hp.use){ ++*use; } HasPtr& operator=(const HasPtr&); ~HasPtr() { if (--*use == 0) { delete use; delete pss; } } private: std::string *pss; int i; std::size_t* use; }; HasPtr& HasPtr::operator=(const HasPtr& rhs) { ++(*rhs.use); if (--*use == 0) { delete use; delete pss; } i = rhs.i; use = rhs.use; pss = rhs.pss; return *this; } |
1. 定义行为像指针的类 1)需要为其定义拷贝构造函数和拷贝赋值运算符,拷贝指针成员本身而不是它指向的string 2)需要自定义析构函数来释放接受string的内存,但是在本例中像指针的类中,需要判断最后一个指向string的类销毁时它才可以释放string 3)让一个类像指针可以用shared_ptr来管理类中的指针资源,但是我们希望自己直接管理资源,可以使用引用计数,也是动态分配的资源。 2. 引用计数 1)引用计数,记录有多少个对象与正在创建的对象共享状态,创建一个对象时初始化为1 2)拷贝构造函数不分配新的计数器,而是拷贝给定对象的数据成员,包括计数器。递增共享的计数器 3)析构函数,递减计数器,若计数器为0,析构函数释放状态 4)拷贝赋值运算符,递增右侧运算符对象的计数器,递减左侧运算对象的计数器。 需要将计数器保存在动态内存中。拷贝或赋值对象时,我们拷贝指向计数器的指针,这样副本和原对象都会指向相同的计数器。 HasPtr p1(“hi”); HasPtr p2(p1); HasPtr p3(p1); 3. 定义一个使用引用计数的类 1)如上代码 4. 类指针的拷贝成员 篡改 引用计数 1)Ps(s),user(user){++*use;} 2)析构函数判断是否计数为0来删除 3)拷贝赋值运算符需要执行拷贝构造函数与析构函数的工作,递增右侧,递减左侧 4)拷贝赋值运算符还需要处理自赋值。先递增rhs中的计数,再递减左侧运算对象的,因为这两个总要执行这个操作,需按照这个顺序就没问题,这样就保证自赋值没问题 |