【C++ Primer】 第十三章 拷贝控制_13.2 拷贝控制和资源管理

目录

13.2.1 行为像值的类 

13.2.2 定义行为像指针的类


13.2.1 行为像值的类 

#include<iostream>
#include<cstring>
using namespace std;

class HasPtr {
public:
	/*
	第一个构造函数接受一个(可选的)string参数。 
	这个构造函数动态分配它自己的string副本。并将指向string的指针保存在ps中。
	*/
	HasPtr(const string& s = string()):ps(new string(s)), i(0) {}//第一个构造函数

	//对于ps指向的string,每个HasPtr对象都有自己的拷贝
	HasPtr(const HasPtr& p):ps(new string(*p.ps)),i(p.i){ }//拷贝构造函数也分配它自己的string副本
	HasPtr& operator=(const HasPtr&);//拷贝赋值运算符
	HasPtr& operator= (const string&);//赋予新string
	string& operator*();//解引用 
	~HasPtr();//析构函数对指针成员ps执行delete,释放析构函数中分配的内存。

private:
	string *ps;
	int i;
};

HasPtr::~HasPtr()
{
	delete ps;//释放string内存
}

inline HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
	if (this == &rhs)//检测自我赋值  myadd
		return *this;

	auto newps = new string(*rhs.ps);//拷贝指针指向的对象
	delete ps; //销毁原string
	ps = newps;//指向新string
	i = rhs.i;//使用内置的int赋值
	return *this;//返回一个此对象的引用
}

HasPtr& HasPtr::operator=(const string &rhs)
{
	*ps = rhs;
	return *this;
}

string& HasPtr::operator*()
{
	return *ps;
}

int main(int argc, char** argv)
{
	HasPtr h("hi,mom!");
	/*
	如果未定义拷贝构造函数,在拷贝HasPtr对象时,合成的拷贝构造函数会建档复制ps成员,使得两个HasPtr指向相同的string。
	当其中一个HasPtr修改string内容时,另一个HasPtr也被改变,这不符合我们的设想。
	如果同时定义了析构函数,情况会更为糟糕,当销毁其中一个HasPtr时,ps指向的strig被销毁,宁一个HasPtr的ps成为空悬指针。
	*/
	HasPtr h2(h);//调用拷贝构造函数;行为类值,h2,h3和h指向不同string
	HasPtr h3 = h;//调用拷贝构造函数
	h2 = "hi,dad!";
	h3 = "hi,son!";
	cout << "h:" << *h << endl;
	cout << "h2:" << *h2 << endl;
	cout << "h3:" << *h3 << endl;
	return 0;
}

【类值拷贝赋值运算符】

    对于一个赋值运算符来说,正确工作是非常重要的,即使是将一个对象赋予它自身,也要能正确工作。一个好的方法是在销毁左侧运算对象资源之前拷贝右侧运算对象

#ifndef MY_STRBLOB_H
#define MY_STRBLOB_H
#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>

using namespace std;
//提前声明,StrBlob中的友类声明所需
class StrBlobPtr;

class StrBlob {
	//StrBlobPtr的成员可以访问StrBolb类的私有部分
	friend class StrBlobPtr;//注意友元类的名称别写错
public:
	/*
	* size_type是一种类型,确保能够保存可能存在的最大向量中的所有元素。
	* 所谓 size_type 就是这个“vector 的 size 的类型”。size就是指vector有多少个元素,这个“多少个”也是个整型值,它的类型就是 size_type。
	* 假如有个vector,你调用size()来得到它有多少个元素,你要把这个值存在一个变量里,那么这个变量该声明为什么类型呢?int 型吗? 
	万一这个vector的元素数超过了 int 的范围呢? 要不就用 long? 万一也不够用呢??
	所以,我们把 size_type 作为这个 size 值的类型,你只要声明一个 size_type 类型的变量就能存下“元素个数”的值。
	顺便一提,在 C 语言中 size_t 指的是系统中最大的整型类型,一般在前述情况下都是使用 size_t 类型的。size_t不是容器概念,而size_type是容器概念,没有容器不能使用。size_type和size_t没有本质区别。
	*/
	typedef vector<string>::size_type size_type;

	StrBlob();
	/*
	*如果一个函数,它的实参 数量不可预知,但是所有的参数的类型相同,我们就可以用这个initializer_list类型的形参来接收。
	*initializer_list 是C++11提供的新类型,是一个类模板
	*/
	StrBlob(initializer_list<string>il);

	StrBlob(vector<string> *p);

	StrBlob(StrBlob& s);//拷贝构造函数

	StrBlob& operator=(StrBlob& rhs);//拷贝赋值运算符

	size_type size() const { return data->size(); }//获取data内存大小
	bool empty() const { return data->empty(); }//获取data指针是否为空
	//添加和删除元素
	void push_back(const string& t)
	{
		data->push_back(t);
	}
	void pop_back();

	//元素访问
	string& front(); //返回的是vector第一个元素的引用。
	const string& front() const;
	string& back();//返回的是vector最后一个元素的引用。
	const string& back() const;

	//提供给StrBolbPtr的接口
	StrBlobPtr begin();//定义StrBlobPtr后才能定义这两个函数,
	StrBlobPtr end();
	//const 版本
	StrBlobPtr begin() const;
	StrBlobPtr end() const;

private:
	shared_ptr<std::vector<std::string>> data;
	//如果data[i]不合法,抛出一个异常
	void check(size_type i, const std::string& msg)const;
};

inline StrBlob::StrBlob():data(make_shared<vector<string>>()){  }

inline StrBlob::StrBlob(initializer_list<string>il):data(make_shared<vector<string>>(il)) {   }

inline StrBlob::StrBlob(vector<string>* p):data(p) {   }

inline StrBlob::StrBlob(StrBlob& s):data(make_shared<vector<string>>(*s.data)){    }

inline StrBlob& StrBlob::operator=(StrBlob& rhs)
{
	data = make_shared<vector<string>>(*rhs.data);
	return *this;
}

inline void StrBlob::check(size_type i, const std::string& msg) const
{
	if (i >= data->size())
		throw out_of_range(msg);
}

inline string& StrBlob::front()
{
	//如果vector为空,check会抛出一个异常
	check(0,"front on empty StrBlob");
	return data->front();
}

//const 版本front
inline const string& StrBlob::front() const
{
	check(0, "front on empty StrBlob");
	return data->front();
}

inline string& StrBlob::back()
{
	//如果vector为空,check会抛出一个异常
	check(0, "back on empty StrBlob");
	return data->back();
}

//const 版本back
inline const string& StrBlob::back() const
{
	check(0, "back on empty StrBlob");
	return data->back();
}

inline void StrBlob::pop_back()
{
	check(0, "pop_back on empty StrBlob");
	return data->pop_back();
}

//当试图访问一个不存在的元素时,StrBlobPtr抛出一个异常
class StrBlobPtr {
	friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
public:
	StrBlobPtr() :curr(0) { }
	StrBlobPtr(StrBlob& a, size_t sz = 0) :wptr(a.data), curr(sz) { }
	StrBlobPtr(const StrBlob& a, size_t sz = 0) :wptr(a.data), curr(sz) { }

	string& deref() const;
	string& deref(int off) const;
	StrBlobPtr& incr();//前缀递增
	StrBlobPtr& decr();//前缀递减

private:
	//若检查成功,check返回一个指向vector的shared_ptr
	shared_ptr<vector<string>>check(size_t, const string&) const;

	//保存一个weak_ptr,意味着底层vector可能会被销毁
	weak_ptr<vector<string>>wptr;
	size_t curr;//在数组中的当前位置
};

inline shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string& msg) const
{
	auto ret = wptr.lock();//vector还存在吗?
	if (!ret)
		throw runtime_error("unbound StrBlobPtr");//未绑定StrBlobPtr
	if (i >= ret->size())
		throw out_of_range(msg);
	return ret;
}

inline string& StrBlobPtr::deref() const
{
	auto p = check(curr, "dereference past end");
	return (*p)[curr];//(*p)是对象所指向的vector
}

inline string& StrBlobPtr::deref(int off) const
{
	auto p = check(curr+off, "dereference past end");
	return (*p)[curr + off];//(*p)是对象所指向的vector
}

//前缀递增:返回递增后的对象的引用
inline StrBlobPtr& StrBlobPtr::incr()
{
	//如果curr已经指向容器的尾后位置,就不能递增它
	check(curr, "increment past end StrBlobPtr");
	++curr;//推进当前位置
	return *this;
}


//前缀递减:返回递减后的对象的引用
inline StrBlobPtr& StrBlobPtr::decr()
{
	//如果curr已经为0,递减它就会产生一个非法下标
	--curr;//递减当前位置
	check(-1, "decrement past end StrBlobPtr");
	return *this;
}

inline StrBlobPtr StrBlob::begin()
{
	return StrBlobPtr(*this);
}

inline StrBlobPtr StrBlob::end()
{
	auto ret = StrBlobPtr(*this, data->size());
	return ret;
}

inline StrBlobPtr StrBlob::begin() const
{
	return StrBlobPtr(*this);
}

inline StrBlobPtr StrBlob::end() const
{
	auto ret = StrBlobPtr(*this, data->size());
	return ret;
}


//StrBlobPtr的比较操作
bool eq(const StrBlobPtr& lhs, const StrBlobPtr& rhs)
{
	auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
	//若底层的vector是同一个
	if (l == r)
		return (!r || lhs.curr == rhs.curr);//则两个指针都是空,或者指向相同元素时,它们相等
	else
		return false;//若指向不同vector,则不可能相等
}

inline bool neq(const StrBlobPtr& lhs, const StrBlobPtr& rhs)
{
	return !eq(lhs, rhs);
}

#endif MY_STRBLOB_H

//主程序
#include<iostream>
using namespace std;

#include "myStrBlob.h"
#include<iostream>
#include<string>
using namespace std;

int main(int arg, char** argv)
{
	StrBlob b1;
	{
		StrBlob b2 = { "a","an","the" };
		b1 = b2;//调用拷贝赋值运算符
		b2.push_back("about");
		cout << "b2大小为" << b2.size() << endl;
		cout << "b2首尾元素为" << b2.front() <<"  "<<b2.back() << endl;
	}

	cout << "b1大小为" << b1.size() << endl;
	cout << "b1首尾元素为" << b1.front() <<"  "<< b1.back() << endl;

	StrBlob b3 = b1;//调用拷贝构造函数
	b3.push_back("next");
	cout << "b3大小为" << b3.size() << endl;
	cout << "b3首尾元素为" << b3.front() <<"   "<< b3.back() << endl;

	cout << "b1全部元素:" << endl;
	for (auto it = b1.begin(); neq(it, b1.end()); it.incr())
		cout << it.deref() << endl;

	return 0;
}

13.2.2 定义行为像指针的类

  1. 对于行为类似指针的类,我们需要为其定义拷贝构造函数和拷贝赋值运算符,来拷贝指针成员本身而不算它指向的string。我们的类仍然需要自己的析构函数来释放接受string参数的构造函数分配的内存。
  2. 令一个类展现类似指针的行为的最好方法是使用shared_ptr来管理类中的资源。拷贝(或赋值)一个shared_ptr会拷贝(赋值)shared_ptr来管理类中的资源。拷贝(或赋值)一个shared_ptr类自己记录有多少用户共享它所指向的对象。当没有用户使用对象时,shared_类负责释放资源。
  3. 有时希望直接管理资源,使用引用计数

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

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

确定在哪里存放计数器?
将计数器保存在动态内存中。当创建一个对象时,我们也分配一个新的计数器。当拷贝或赋值对象时,我们拷贝指向计数器的指针。使用这种方法,副本和原对象都会指向相同的计数器。

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

class HasPtr{
public:
    //构造函数分配新的string和新的计数器,将计数器置为1
    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;//用来记录有多少个对象共享*ps的成员
};

类指针的拷贝成员“篡改”引用计数

        析构函数不能无条件地delete ps——可能还有其他对象指向这块内存。析构函数应该递减引用计数,指出共享string的对象少了一个。如果计数器变为0,则析构函数释放ps和use指向的内存。

HasPtr::~HasPtr(){
    if(--*use == 0){//如果引用计数变为0
        delete ps;//释放string内存
        delete use;//释放计数器内存
    }
}

         拷贝赋值运算符必须处理自赋值。先递增rhs中的计数,然后再递减左侧运算对象中的计数来实现这一点。通过这种方法,当两个对象相同时,在我们检查ps(及use)释放应该释放之前,计数器就已经被递增过了。

HasPtr& HasPtr::operator=(const HasPtr& rhs) {
	++* rhs.use;//递增右侧运算对象的引用计数
	if (-- * use == 0) { //然后递减本对象的引用计数
		delete ps;//如果没有其他用户
		delete use;//释放本对象分配的成员
	}
	ps = rhs.ps;//将数据从rhs拷贝到本对象
	i = rhs.i;
	use = rhs.use;
	return *this;//返回本对象
}

#include<iostream>

using namespace std;

class HasPtr {
public:
    //构造函数分配新的string和新的计数器,将计数器置为1
    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& operator=(const string&);//赋予新string

    string& operator*();//解引用

    ~HasPtr();
private:
    std::string* ps;
    int i;
    std::size_t* use;//用来记录有多少个对象共享*ps的成员
};

HasPtr::~HasPtr() {
    if (--*use == 0) {//如果引用计数变为0
        delete ps;//释放string内存
        delete use;//释放计数器内存
    }
}

HasPtr& HasPtr::operator=(const HasPtr& rhs) {
    ++* rhs.use;//递增右侧运算对象的引用计数
    if (--*use == 0) { //然后递减本对象的引用计数
        delete ps;//如果没有其他用户
        delete use;//释放本对象分配的成员
    }
    ps = rhs.ps;//将数据从rhs拷贝到本对象
    i = rhs.i;
    use = rhs.use;
    return *this;//返回本对象
}
HasPtr& HasPtr::operator=(const string &rhs)
{
    *ps = rhs;
    return *this;
}

string& HasPtr::operator*()
{
    return *ps;
}

int main(int arg, char** argv)
{
    HasPtr h("hi mom!");
    HasPtr h2 = h;//未分配新string,h2和h指向相同的string 
    h = "hi dad!";
    cout << "h:" << *h << endl;
    cout << "h2:" << *h2 << endl;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冷凝女子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值