前景提要
一般我们使用过的类,分配的资源都与对应对象生存期一致。比如每个vector都有自己的元素,当我们拷贝一个vector时,原vector和副本vector的元素是分离的
vector<string> v1;
//作用域
{
vector<string> v2 = {"a", "an", "the"};
v1 = v2;
}
//作用域结束v2被销毁,其元素也被销毁
//v1保存了v2元素的拷贝
我们可以定义一个类Blob。与平时的容器不同,它对象的不同拷贝之间共享相同的元素。比如当我们拷贝一个Blob时,原Blob对象及其拷贝应该引用相同的底层元素
Blob<string> b1;
//作用域
{
Blob<string> b2 = {"a", "an", "the"};
b1 = b2;
}
//b2被销毁,但其元素没被销毁
//b1指向最初由b2创建的元素
这可以通过动态内存实现。
框架
class StrBlob
{
public:
typedef vector<string>::size_type size_type;
StrBlob();
StrBlob(initializer_list<string> il);
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
//添加删除元素
void push_back(const string &t) { data->push_back(t); }
void pop_back();
//元素访问
string& front();
string& back();
private:
//使用shred_ptr管理动态分配的vector
//记录有多少StrBlob共享相同的vector
//并在vector的最后一个使用者被销毁时释放vector
shared_ptr<vector<string>> data;
//如果data[i]不合法则抛出异常
void check(size_type i, const string &msg) const;
};
细节
构造函数
默认构造函数分配一个空vector给data指向。接受一个initializer_list的构造函数将其参数传递给对应的vector构造函数,此构造函数通过拷贝列表中的值来初始化vector的元素
StrBlob() : data(make_shared<vector<string>>()) {}
StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}
元素访问成员函数
pop_back、front和back操作会访问vector中的元素。这些操作在试图访问元素之前必须检查元素是否存在。这三个都要用到同一个函数,所以将这个公共的工具函数定义为private
void StrBlob::check(size_type i, const string &msg) const
{
if (i >= data->size())
throw out_of_range(msg);
}
string& StrBlob::front()
{
check(0, "front on empty StrBlob");
return data->front();
}
string& StrBlob::back()
{
check(0, "back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob");
return data->pop_back();
}
//pop_back和push_back不需要const版本
//如果是const版本,常量对象将能够调用这两个函数
//可是常量对象不能修改自身内容,产生矛盾
延申
为了展示weak_ptr的用处,我们为StrBlob定义一个伴随指针类。这个类命名为StrBlobPtr,保存一个weak_ptr,不会影响一个给定的StrBlob所指向的vector的生存期,但是可以阻止用户访问一个不再存在的vector。
StrBlobPtr有两个数据成员:wptr,要么为空,要么指向StrBlob中的vector;curr,保存当前对象所表示的元素的下标。和StrBlob一样,我们也有一个check来检查解引用StrBlobPtr是否安全。
class StrBlobPtr
{
public:
StrBlobPtr() : curr(0) {}
StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
string& deref() const;
StrBlobPtr& incr(); //前缀递增
private:
//检查无误则返回一个指向vector的shared_ptr
shared_ptr<vector<string>> check(size_t, const string&) const;
weak_ptr<vector<string>> wptr;
size_t curr;
};
实现细节
构造函数实现
默认构造函数生成一个空的StrBlobPtr,将curr显式初始化为0,将wptr隐式初始化为空。第二个构造函数接受一个StrBlob引用和一个可选索引值,令其指向给定的StrBlob对象的shared_ptr中的vector,并将curr初始化为sz。
StrBlobPtr() : curr(0) {}
StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
可以看到,我们不能将StrBlobPtr绑定到一个const StrBlob对象。因为构造函数接受一个非const StrBlob对象的引用。
check函数
check函数和StrBlob中的不一样,这里的check要检查指针指向的vector是否存在:
shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
auto ret = wptr.lock();
if (!ret)
throw runtime_error("unbound StrBlobStr");
if (i >= ret->size())
throw out_of_range(msg);
return ret;
}
先用lock检查vector是否存在,如果已经销毁,lock会返回空指针。如果没被销毁,则检查下标是否越界,如果合法则返回从lock处获得的shared_ptr。
指针操作实现
deref和incr函数分别用来解引用和递增StrBlobPtr
dere函数先调用check检查vector是否安全,且下标是否越界
string& StrBlobPtr::deref() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];
//p是指向vector的shared_ptr,对p解引用获得vector
//然后用下标运算符[]得到curr下的内容
}
StrBlobPtr& StrBlobPtr::incr()
{
//如果curr已经指向容器尾后位置,就不能递增它
check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
因为StrBlobPtr会访问StrBlob中的data,所以要声明为StrBlob的友元类。再为StrBlob定义begin和end操作,返回一个指向它自身的StrBlobPtr
//StrBlob中有友元声明,所以这里必须前置声明一下
class StrBlobPtr;
class StrBlob
{
friend class StrBlobPtr;
StrBlobPtr begin() { return StrBlobPtr(*this); }
StrBlobPtr end()
{
auto ret = StrBlobPtr(*this, data->size());
//注意是指向最后一个元素后一个位置
return ret;
}
};
模板类Blob
template <typename T>
class Blob
{
public:
typedef T value_type;
typedef typename vector<T>::size_type size_type;
//构造函数
Blob();
Blob(initializer_list<T> il);
//元素数目
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
//添加删除元素
void push_back(const T &t) { data->push_back(t); }
//移动版本
void push_back(T &&t) { data->push_back(std::move(t)); }
void pop_back();
//元素访问
T& back();
T& operator[](size_type i);
private:
shared_ptr<vector<T>> data;
//如果data[i]不合法则抛出异常
void check(size_type i, const string &msg) const;
};
模板类构造函数
template <typename T>
Blob<T>::Blob() : data(make_shared<vector<T>>()) {}
template <typename T>
Blob<T>::Blob(initializer_list<T> il) : data(make_shared<vector<T>>(il)) {}
模板类成员函数
template <typename>
void Blob<T>::check(size_type i, const string &msg) const
{
if (i >= data->size())
throw out_of_range(msg);
}
template <typename T>
T& Blob<T>::back()
{
check(0 ,"back on empty Blob");
return data->back();
}
template <typename T>
T& Blob<T>::operator[](size_type i)
{
check(0 ,"subscript out of range");
return (*data)[i];
}
template <typename T>
void Blob<T>::pop_back()
{
check(0, "pop_back on empty Blob");
data->pop_back();
}