类设计者的工具(二):拷贝控制示例

本文为《C++ Primer》的读书笔记

设计思路

作为类需要拷贝控制的例子, 我们将概述两个类的设计, 这两个类可能用于邮件处理应用中

两个类命名为MessageFolder, 分别表示电子邮件消息和消息目录。每个Message对象可以出现在多个Folder中。但是,任意给定的Message的内容只有一个副本

为了记录Message位于哪些Folder 中, 每个Message 都会保存一个它所在Folder 的指针的set, 同样的, 每个Folder都保存一个它包含的Message的指针的set

我们的Message 类会提供saveremove操作, 来向一个给定Folder添加一条或是删除一条Message。为了创建一个新的Message, 我们会指明消息内容, 但不会指出Folder。为了将一条Message放到一个特定Folder 中, 我们必须调用save

  • 当我们拷贝一个Message时, 副本和原对象将是不同的Message 对象, 但两个Message 都出现在相同的Folder中。因此, 拷贝Message 的操作包括消息内容和Folder 指针set的拷贝。而且,我们必须在每个包含此消息的Folder中都添加一个指向新创建的Message的指针

  • 销毁一个Message时, 必须从包含此消息的所有Folder中删除指向此Message的指针

  • 当我们将一个Message对象赋予另一个Message对象时,左侧Message的内容会被右侧Message的内容所替代。我们还必须更新Folder集合,从原来包含左侧MessageFolder中将它删除, 并将它添加到包含右侧MessageFolder

我们可以看到, 析构函数和拷贝赋值运算符都必须从包含一条Message的所有Folder中删除它。类似的,拷贝构造函数和拷贝赋值运算符都要将一个Message添加到给定的一组Folder中。我们将定义两个private的工具函数来完成这些工作

拷贝赋值运算符通常执行拷贝构造函数和析构函数中也要做的工作。这种情况下, 公共的工作应该放在private的工具函数中完成

Folder类也需要类似的拷贝控制成员, 来添加或删除它保存的Message。假定Folder类包含名为addMsgremMsg的成员

Message

class Message {
	friend class Folder;
public:
	// folders被隐式初始化为空集合
	explicit Message(const std::string &str = ""):
		contents(str) { }
	// 拷贝控制成员, 用来管理指向本Message的指针
	Message(const Message&); 
	Message& operator= (const Message&);
	~Message(); 
	// 从给定Folder集合中添加/删除本Message
	void save(Folder&);
	void remove(Folder&);
private:
	std::string contents; // 实际消息文本
	std::set<Folder*> folders; // 包含本Message的Folder
	// 拷贝构造函数、拷贝赋值运算符和析构函数所使用的工具函数
	// 将本Message添加到指向参数的Folder中
	void add_to_Folders(const Message&);
	// 从folders中的每个Folder中删除本Message
	void remove_from_Folders();
};
void Message::save(Folder &f)
{
	folders.insert(&f); 	//将给定Folder添加到我们的Folder 列表中
	f.addMsg(this); 		//将本Message添加到f的Message 集合中
}
void Message::remove(Folder &f)
{
	folders.erase(&f); 		//将给定Folder从我们的Folder 列表中删除
	f.remMsg(this); 		//将本Message从f的Message 集合中删除
}
// 将本Message添加到指向m的Folder中
void Message::add_to_Folders(const Message &m)
{
	for (auto f : m.folders) 	//对每个包含m的Folder
		f->addMsg(this); 		//向该Folder添加一个指向本Message的指针
}
Message::Message(const Message &m):
	contents(m.contents), folders(m.folders)
{
	add_to_Folders(m); // 将本消息添加到指向m的Folder中
}
// 从对应的Folder 中删除本Message
void Message::remove_from_Folders()
{
	for (auto f : folders) 	//对folders 中每个指针
		f->remMsg(this); 	//从该Folder 中删除本Message
}
// 编译器自动调用string的析构函数来释放contents, 
// 并自动调用set的析构函数来清理集合成员使用的内存
Message::~Message()
{
	remove_from_Folders();
}
Message& Message::operator=(const Message &rhs)
{
	// 通过先删除指针再插入它们来处理自赋值情况
	remove_from_Folders(); 		//更新已有Folder
	contents = rhs.contents; 	//从rhs拷贝消息内容
	folders = rhs.folders; 		//从rhs拷贝Folder 指针
	add_to_Folders(rhs); 		//将本Message添加到那些Folder中
	return *this;
}
void swap(Message &lhs, Message &rhs)
{
	using std::swap; // 在本例中严格来说并不需要, 但这是一个好习惯
	// 将每个消息的指针从它(原来)所在Folder中删除
	for (auto f : lhs.folders)
		f->remMsg(&lhs);
	for (auto f: rhs.folders)
		f->remMsg(&rhs);
	// 交换contents和Folder指针set
	swap(lhs.folders, rhs.folders); 	// 使用swap(set&, set&)
	swap(lhs.contents, rhs.contents); 	// swap(string&, string&)
	// 将每个Message的指针添加到它的(新) Folder中
	for (auto f: lhs.folders)
		f->addMsg(&lhs);
	for (auto f: rhs.folders)
		f->addMsg(&rhs);
}

Message 类的移动操作

定义了自己的拷贝构造函数和拷贝赋值运算符的类通常也会从移动操作受益。通过定义移动操作,Message类可以使用stringset的移动操作来避免拷贝contentsfolders成员的额外开销。但是, 除了移动folders成员, 我们还必须更新每个指向原MessageFolder。我们必须删除指向旧Message的指针, 并添加一个指向新Message的指针

移动构造函数和移动赋值运算符都需要更新Folder指针,因此我们首先定义一个操作来完成这一共同的工作:

// 从本Message移动Folder指针
void Message::move_Folders(Message *m)
{
	folders = std::move(m->folders); //使用set的移动赋值运算符
	for (auto f : folders) { //对每个Folder
		f->remMsg(m); //从Folder中删除旧Message
		f->addMsg(this); //将本Message添加到Folder中
	}
	m->folders.clear(); // 确保销毁m是无害的
						// 由于Message的析构函数遍历folders, 我们希望能确定set是空的
}

值得注意的是, 向set插入一个元素可能会抛出一个异常(bad_alloc)。因此,Message的移动构造函数和移动赋值运算符可能会抛出异常。因此我们未将它们标记为noexcept

Message::Message(Message &&m): contents(std::move(m.contents))
{
	move_Folders(&m); // 移动folders并更新Folder指针
}
Message& Message::operator=(Message &&rhs)
{
	if (this ! = &rhs) { // 直接检查自赋值情况
	remove_from_Folders();	// 销毁左侧运算对象的旧状态
	contents = std::move(rhs.contents); // 移动赋值运算符
	move_Folders(&rhs); // 重置Folders指向本Message
	return *this;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值