专栏简介:本专栏主要面向C++初学者,解释C++的一些基本概念和基础语言特性,涉及C++标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级程序设计技术。希望对读者有帮助!
拷贝控制示例
虽然通常来说分配资源的类更需要拷贝控制,但资源管理并不是一个类需要定义自己的拷贝控制成员的唯一原因。一些类也需要拷贝控制成员的帮助来进行簪记工作或其他操作。
作为类霁要拷贝控制来进行蕹记操作的例子,我们将概述两个类的设计,这两个类可能用于邮件处理应用中。两个类命名为Message和Folder,分别表示电子邮件(或者其他类型的)消息和消息目录。每个Message对象可以出现在多个Folder中。但是,任意给定的Message的内容只有一个副本。这样,如果一条Message的内容被改变,则我们从它所在的任何Folder来浏览此Message时,都会看到改变后的内容。
为了记录Message位于哪些Folder中,每个Message都会保存一个它所在Folder的指针的set,同样的,钗个Folder都保存一个它包含的Message的指针的
set。图13.1说明了这种设计思路。
graphLR;
Folder[Folder]-->Message[Message];
Message-->Folder[Folder];
Folder-->Message1[Message];
Message1-->Folder
图13.1:Message和Folder类设计
我们的Message类会提供save和remove操作,来向一个给定Folder添加一条Message或是从中删除一条Message。为了创建一个新的Message,我们会指明消息内容,但不会指出Folder。为了将一条Message放到一个特定Folder中,我们必须调用save。
当我们拷贝一个Message时,副本和原对象将是不同的Message对象,但两个Message都出现在相同的Folder中。因此,拷贝Message的操作包括消息内容和Folder指针set的拷贝。而且,我们必须在每个包含此消息的Foldez中都添加一个指向新创建的Message的指针。
当我们销毁一个Message时,它将不复存在。因此,我们必须从包含此消息的所有
Folder中删除指向此Message的指针。
当我们将一个Message对象赋予另一个Message对象时,左侧Message的内容会被右侧Message的内容所替代.我们还必须更新Eolder集合,从原来包含左侧Message的Foldez中将它删除,并将它添加到包含右侧Message的Foldez中。
观察这些操作,我们可以看到,析构函数和拷贝赋值运算符都必须从包含一条Message的所有Folder中删除它。类似的,拷贝构造函数和拷贝赋值运算符都要将一个Message添加到给定的一组Folder中。我们将定义两个private的工具函数来完成这些工作。
RBst。指贝赋值运算符通常执行拷贝构造函数和析构函数中也要做的工作这种情况的工作应该放在Private的工具函数中完成:
Folder类也需要类似的拷贝控制成员,来添加或删除它保存的Message。821
我们将Folder类的设计和实现留作练习。但是,我们将假定Folder类包含名为addMsg和remMsg的成员,分别完成在给定Folder对象的消息集合中添加和删除Message的工作。
##Message类
根据上述设计,我们可以编写Message类,如下所示:
class Message{
friend class Folder;
public:
//folders被隐式初始化为空集合
explicit Message(conststd::string&str=""):
contents(str){}
//指贝控制成员,用来管理指向本Message的指针
Message(const Message&);//拷贝构造函数
Message&operator=(const Messageg);//指贝赋值运算符
~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(constMessage&);
//从folders中的每个Folder中删除本Message
void remove_from_Folders();
这个类定义了两个数据成员:contents,保存消息文本;folders,保存指向本Message所在Folder的指针。接受一个string参数的构造函数将给定string拷贝给contents,并将folders(隐式)初始化为空集。由于此构造函数有一个默认参数,因此它也被当作Message的默认构造函数。
save和remove成员
除拷贝控制成员外,Message类只有两个公共成员:save,将本Message存放在给定Folder中;remove,删除本Message:
voidMessage::save(Folder&f)
{
folders.insert(gf);“//将给定Folder添加到我们的Folder列表中
f.addMsg(this)}//将本Message添加到f的Message集合中
}
voidMessage::remove(Folder&f)
{
folders.erase(&f);//将给定Folder从我们的Folder列表中删除
f.remMsg(thts);//将本Message从的Message集合中删除
}
为了保存(或删除)一个Message,需要更新本Message的folders成员。当save一个Message时,我们应保存一个指向给定Folder的指针;当remove一个Message时,我们要删除此指针。
这些操作还必须更新给定的Folder。更新一个Folder的任务是由Folder类的addMsg和remMsg成员来完成的,分别添加和删除给定Message的指针。
Message类的拷贝控制成员
当我们拷贝一个Message时,得到的副本应该与原Message出现在相同的Folder中。因此,我们必须遍历Folder指针的set,对每个指向原Message的Foldez添加一个指向新Message的指针。拷贝构造函数和拷贝赋值运算符都需要做这个工作,因此我们定义一个函数来完成这个公共操作:
//将本Message添加到指向m的Folder中
voidMessage::add_to_Folders(constMessage&m)
{
for(autof:m.folders)//对每个包含m的Folder
f->addMsg(this);//向该Folder添加一个指向本Message的指针
}
此例中我们对m.folders中每个Folder调用addMsg。函数addMsg会将本Message的指针添加到每个Folder中。
Message的拷贝构造函数拷贝给定对象的数据成员:
Message::Message(constMessage&m):
contents(m.contents),folders(m.folders)
{
add_to_Folders(m);//将本消息添加到指向m的Folder中
}
并调用add_toFolders将新创建的Message的指针添加到每个包含原Message的Folder中。
Message的析构函数
当一个Message被销毁时,我们必须从指向此Message的Folder中删除它。拷贝赋值运算符也要执行此操作,因此我们会定义一个公共函数来完成此工作:
//从对应的Folder中删除本Message
void Message::remove_from_Folders()
{
for(auto:folders)//对folders中每个指针
f->remMsg(this);//从该Folder中删除本Message
}
函数remove_from_Folders的实现类似add_to_Folders,不同之处是它调用remkMsg来删除当前Message而不是调用addMsg来添加Message。
有了remove_from_Folders函数,编写析构丽数就很简单了:
Message::“Message()
{
remove_from_Folders();
}
调用remove_from_Folders确保没有任何Folder保存正在销毁的Message的指针。编译器自动调用string的析构函数来释放contents,并自动调用set的析构函数来清理集合成员使用的内存。
Message的拷贝赋值运算符
与大多数赋值运算符相同,我们的Message类的拷贝赋值运算符必须执行拷贝构造函数和析构函数的工作。与往常一样,最重要的是我们要组织好代码结构,使得即使左侧和右侧运算对象是同一个Message,拷贝赋值运算符也能正确执行。
在本例中,我们先从左侧运算对象的folders中删除此Message的指针,然后再将指针添加到右侧运算对象的folders中,从而实现了自赋值的正确处理:
Messageg 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;
}
如果左侧和右侧运算对象是相同的Message,则它们具有相同的地址。如果我们在add_to_Folders之后调用remove_from_Folders,就会将此Message从它所在的所有Folder中删除。
Message的swap函数
标准库中定义了string和set的swap版本。因此,如果为我们的Message类定义它自己的swap版本,它将从中受益。通过定义一个Message特定版本的swap,我们可以避免对contents和folders成员进行不必要的拷贝。
但是,我们的swap函数必须管理指向被交换Message的Folder指针。在调用swap(ml,m2)之后,原籼指向ml的Folder现在必须指向m2,反之亦然。
我们通过两遍扫描folders中每个成员来正确处理Folder指针。第一遍扫描将Message从它们所在的Folder中删除。接下来我们调用swap来交换数据成员。最后对folders迹行第二道扫描来添加交换过的Message:
void swap(Message &lhs,Message&rhs)
{
usingstd::swap;//在本例中严格来说并不需要,但这是一个好习惯
//将每个消息的指针从它(原来)所在Folder中删除
for(autof:lhs.folders)
{f->remMsg(&1lhs)}
for(autof:rhs.folders)
f->remMsg(&zhs);
//交换contents和Folder指针set
swap(lhs.folders,rhs.folders);//使用swap(set&,set&)
swap(lhs.contents,rhs.contents);//swap(string&,string&)
//将每个Message的指针添加到它的(新)Folder中
for(autof:lhs.folders)
f->addMsg(&lhs);
for(autof:rhs.folders)
f->addMsg(&rhs);
}