动态内存使用示例——共享对象的类

前景提要

一般我们使用过的类,分配的资源都与对应对象生存期一致。比如每个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();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、 FACTORY —追 MM 少不了请吃饭了, 麦当劳的鸡翅和肯德基的鸡翅都是 MM 爱吃的东西, 虽然口味有所不同, 但不管你带 MM 去麦当劳或肯德基, 只管向服务员说“来四个鸡翅”就行 了。麦当劳和肯德基就是生产鸡翅的 Factory 工厂模式:客户和工厂分开。消费者任何时候需要某种产品,只需向工厂请求即可。消 工厂模式 费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂也要做相应的修改。如:如 何创建及如何向客户端提供。 2、BUILDER — MM 最爱听的就是“我爱你”这句话了,见到不同地方的 MM,要能够用她们的 、 方言跟她说这句话哦,我有一个多种语言翻译机,上面每种语言都有一个按键,见到 MM 我只要按对应的键, 它就能够用相应的语言说出“我爱你”这句话了, 国外的 MM 也可以轻松 搞掂,这就是我的“我爱你”builder。 (这一定比美军在 伊拉克用的翻译机好卖) 建造模式: 从而使一个建造过程生成具有不 建造模式 将产品的内部表象和产品的生成过程分割开来, 同的内部表象的产品对象。 建造模式使得产品内部表象可以独立的变化, 客户不必知道产品 内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。 3、FACTORY METHOD —请 MM 去麦当劳吃汉堡,不同的 MM 有不同的口味,要每个都记住 、 是一件烦人的事情,我一般采用 Factory Method 模式,带着 MM 到服务员那儿,说“要一个 汉堡”,具体要什么样的汉堡呢,让 MM 直接跟服务员说就行了。 工厂方法模式: 而是将具体创建的工作交给子去做, 工厂方法模式 核心工厂不再负责所有产品的创建, 成为一个抽象工厂角色, 仅负责给出具体工厂必须实现的接口, 而不接触哪一个产品应 当被实例化这种细节。 4、 、 PROTOTYPE —跟 MM 用 QQ 聊天, 一定要说些深情的话语了, 我搜集了好多肉麻的情话, 需要时只要 copy 出来放到 QQ 里面就行了, 这就是我的情话 prototype 了。 (100 块钱一份, 你要不要) 原始模型模式: 原始模型模式 通过给出一个原型对象来指明所要创建的对象型,然后用复制这个原 型对象的方法创建出更多同型的对象。 原始模型模式允许动态的增加或减少产品, 产品 不需要非得有任何事先确定的等级结构, 原始模型模式适用于任何的等级结构。 缺点是每 一个都必须配备一个克隆方法。 5、 、 SINGLETON —俺有 6 个漂亮的老婆, 她们的老公都是我, 我就是我们家里的老公 Sigleton, 她们只要说道“老公”,都是指的同一个人,那就是我(刚才做了个梦啦,哪有这么好的事) 单例模式: 而且自行实例化并向整个系统提供这个实 单例模式 单例模式确保某一个只有一个实例, 例单例模式。单例模式只应在有真正的“单一实例”的需求时才可使用。 结构型模式 6、ADAPTER —在朋友聚会上碰到了一个美女 Sarah,从香港来的,可我不会说粤语,她不 、 会说普通话,只好求助于我的朋友 kent 了,他作为我和 Sarah 之间的 Adapter,让我和 Sarah 可以相互交谈了(也不知道他会不会耍我) 适配器模式: 从而使原本因接口原因不 适配器模式 把一个的接口变换成客户端所期待的另一种接口, 匹配而无法一起工作的两个能够一起工作。 适配可以根据参数返还一个合适的实例给客 户端。 7、BRIDGE —早上碰到 MM,要说早上好,晚上碰到 MM,要说晚上好;碰到 MM 穿了件新 、 衣服, 要说你的衣服好漂亮哦, 碰到 MM 新做的发型, 要说你的头发好漂亮哦。 不要问我“早 上碰到 MM 新做了个发型怎么说”这种问题,自己用 BRIDGE 组合一下不就行了 桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关 桥梁模式 联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是 继承关系,从而使两者可以独立的变化。 8、COMPOSITE —Mary 今天过生日。“我过生日,你要送我一件礼物。”“嗯,好吧,去商店, 、 你自己挑。”“这件 T 恤挺漂亮,买,这条裙子好看,买,这个包也不错, 买。”“喂,买了 三件了呀,我只答应送一件礼物的哦。”“什么呀,T 恤加裙子加包包,正好配成一套呀,小 姐,麻烦你包起来。”“……”,MM 都会用 Composite 模式了,你会了没有? 合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就 合成模式 是一个处理对象的树结构的模式。 合成模式把部分与整体的

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值