拷贝控制和资源管理

类的行为可以像一个值或指针:

类的行为像一个值,意味着它应该也有自己的状态。当我们拷贝一个像值得对象时,副本与原对象是独立的,改变副本不会对原对象有任何影响,反之亦然。

类的行为像指针,将共享状态,拷贝这样的一个类的对象时,副本和原对象使用相同的底层数据,改变副本也会改变原对象,反之亦然。

行为像值的类

提供类值的行为,对于类管理的资源,每个对象都应该拥有一份自己的拷贝。

class Hasptr{
public:
	Hasptr(const std::string &s=std::string()):ps(new std::string(s)),i(0){}
	Hasptr(const Hasptr &p):ps(new std::string(*p.ps)),i(p.i){}
	Hasptr& operator=(const Hasptr&);
	~Hasptr(){delete ps;}
private:
	std::string *ps;
	int i;
}

类值拷贝赋值运算符

赋值运算符组合了析构函数和构造函数的操作:

  • 类似析构函数,赋值操作会销毁左侧运算对象的值;
  • 类似拷贝构造函数,赋值操作会从右侧对象拷贝数据。

需要注意的是:

  • 如果将一个对象赋予自身,赋值运算符必须能正常工作;
  • 异常安全,即当异常发生时,能将左侧运算对象置于一个有意义的状态。
Hasptr& Hasptr::operator = (const Hasptr &rhs)
{
	auto newp =  new string(*rhs.ps);	//拷贝底层的string
	delete ps;		//释放旧内存
	ps = newp;		//右侧对象拷贝数据到本对象
	i = rhs.i;
	return *this;	//返回本对象
}

行为像指针的类

引用计数

引用计数的工作方式如下:

  • 除了初始化对象外,每个构造函数(拷贝构造函数除外)还要构建一个引用计数,用来记录有多少对象与正在创建的对象共享状态。当创建一个对象时,只有一个对象共享状态,将此计数器初始化为1。
  • 拷贝构造函数不分配新的计数器,而是拷贝给定对象数据成员,包括计数器。拷贝构造函数递增共享的计数器,指出给定对象的状态又被一个新的用户所共享。
  • 析构函数递减计数器,指出共享状态的用户少了一个,如果计数器为0,则析构函数释放状态。
  • 拷贝赋值运算符递增右侧运算对象的计数器,递减左侧运算对象的计数器。如果左侧运算对象的计数器为0,意味着它的共享状态没有用户了,拷贝赋值运算符就必须销毁状态。

计数器不能是 Hasptr 对象的成员:

Hasptr p1("Hiya");
Hasptr p2(p1);	
Hasptr p3(p1);	//p1,p2,p3指向相同的string

如果引用计数保存在每个对象中,当创建 p3 时,可以递增 p1 中的计数器并将其拷贝到 p3,但是 p2 中的计数器无法更新。

解决办法是将计数器保存在动态内存中,当创建一个对象时,也分配一个新的计数器,当拷贝或赋值对象时,拷贝指向计数器的指针,使用此方法可以保证副本和原对象指向相同的计数器。

定义一个使用引用计数的类

class Hasptr
{
public:
	Hasptr(const std::string &s = std::string()):ps(new std::string(s),i(0),use(new std::size_t(1))){}
	Hasptr(const Hasptr &p):ps(p.ps),i(p.i),use(p.use){++*use};
	Hasptr& operator=(const Hasptr&);
	~Hasptr();
private:
	std::string *ps;
	int i;
	std::size_t *use;
}

析构函数需要检查引用计数是否为0:

Hasptr ::~Hasptr()
{
	if(--*use == 0){
		delete ps;
		delete use;
	}
}

拷贝赋值运算符:

Hasptr& Hasptr::operator=(const Hasptr& rhs)
{
	++*rhs.use;		//递增右侧对象的引用计数
	if(--*use == 0)	//递减和检测本对象的引用计数
	{
		delete ps;
		delete use;
	}
	
	ps = rhs.ps;
	i = rhs.i;
	use = rhs.use;
	
	return *this;
}
原文地址:https://www.cnblogs.com/xiaojianliu/p/12496728.html