目录
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 定义行为像指针的类
- 对于行为类似指针的类,我们需要为其定义拷贝构造函数和拷贝赋值运算符,来拷贝指针成员本身而不算它指向的string。我们的类仍然需要自己的析构函数来释放接受string参数的构造函数分配的内存。
- 令一个类展现类似指针的行为的最好方法是使用shared_ptr来管理类中的资源。拷贝(或赋值)一个shared_ptr会拷贝(赋值)shared_ptr来管理类中的资源。拷贝(或赋值)一个shared_ptr类自己记录有多少用户共享它所指向的对象。当没有用户使用对象时,shared_类负责释放资源。
- 有时希望直接管理资源,使用引用计数。
引用计数的工作方式如下:
- 除了初始化对象外,每个构造函数(拷贝构造函数除外)还要创建一个引用计数,用来记录有多少对象与正在创建的对象共享状态。当我们创建一个对象时,只有一个对象共享状态,因此将计数器初始化为1。
- 拷贝构造函数不分配新的计数器,而是拷贝给定对象的数据成员,包括计数器。拷贝构造函数递增共享的计数器,指出给定对象的状态又被一个新用户所共享。
- 析构函数递减计数器,指出共享状态的用户少了一个。如果计数器变为0,则析构函数释放状态。
- 拷贝赋值运算符递增右侧运算对象的计数器,递减左侧运算对象的计数器。如果左侧运算对象的计数器变为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;
}