《C++ Primer》第13章 拷贝控制
13.4节 拷贝控制示例 习题答案
练习13.33:为什么Message的成员save和remove的参数是一个Folder&?为什么我们不将参数定义为Folder或是const Folder&?
【出题思路】
理解两个成员函数的语义,以及这种语义导致对参数类型的要求。
【解答】
首先,我们需要将给定Folder的指针添加到当前Message的folders集合中。这样,参数类型就不能是Folder,必须是引用类型。否则,调用save时会将实参拷贝给形参,folders.insert添加的就是形参(与局部变量一样在栈中,save执行完毕就被销毁)的指针,而非原始Folder的指针。而参数为引用类型,就可以令形参与实参指向相同的对象,对形参f取地址,得到的就是原始Folder(实参)的指针。
其次,我们需要调用addMsg将当前Message的指针添加到Folder的messages集合中,这意味着我们修改了Folder的内容,因此参数不能是const的。
练习13.34:编写本节所描述的Message。
【出题思路】
练习使用拷贝控制成员实现簿记工作。
【解答】
参考书中本节内容完成Message类的编写,并与配套网站代码进行对比。
练习13.35:如果Message使用合成的拷贝控制成员,将会发生什么?
【出题思路】
理解类似本问题的簿记工作需求为何需要拷贝控制成员。
【解答】
Message类包含两个数据成员:content为string类型,folders为set。这两个标准库类都有完备的拷贝控制成员,因此Message使用合成的拷贝控制成员的话,简单拷贝这两个成员也能实现正确拷贝。
但是,本问题的需求不仅如此。
当拷贝Message时,不仅要拷贝这个Message在哪些Folder中,还要将Message加到每个Folder中——调用addMsg。
类似的,当销毁Message时,需要将它从所有Folder中删除——调用remMsg。
因此,不能依赖合成的拷贝控制成员,必须设计自己的版本来完成这些簿记工作。
练习13.36:设计并实现对应的Folder类。此类应该保存一个指向Folder中包含的Message的set。【出题思路】
本题练习利用拷贝控制成员实现正确的簿记操作。
【解答】
首先,Folder类有唯一的数据成员,保存文件夹中所有消息的指针:
set<Message*> msgs;
其次,应该实现Message类所用到的两个函数addMsg和remMsg,将消息添加到和删除出文件夹,直接调用msgs的操作即可:
void addMsg(Message *m) {msgs.insert(m);}
void remMsg(Message *m) {msgs.erase(m);}
类似Message类将自身添加到和删除出所有Folder的成员函数,Folder也应有将自身添加到和删除出所有Message的folders集合的成员函数,方便拷贝控制成员调用:
void Folder::add_to_Message(const Folder &f)
{
for(auto msg: f.msgs)
msg->addFldr(this);//将这个Folder添加到所有Message中
}
void Folder::remove_from_Msgs()
{
while(!msgs.empty(0)//将这个Folder从它所有Message中删除
(*msgs.begin())->remove(*this);
}
用到两个Message的辅助成员函数:
void addFldr(Folder *f) {folers.insert(f);}//添加给定Folder
void Message::remove(Folder &f)//删除给定Folder
{
folders.erase(&f);//将Folder从此Message中删除
f.remMsg(this); //反向:将Message也从Folder中删除
}
接下来就可以定义拷贝控制成员了,首先是拷贝构造函数,它先拷贝msgs集合,然后调用add_to_Messages添加到它所有Message的folders集合中:
Folder::Folder(const Folder& f): msgs(f.msgs)
{
add_to_Message(f);//将Folder添加到它所有Message的folders中
}
析构函数调用remove_from_Msgs从所有Message中删除本Folder:
Folder::~Folder()
{
remove_from_Msgs();
}
拷贝赋值运算符首先将Folder从每个旧Message中删除,然后从右侧Folder拷贝Message集合,最后将自身添加到每个新Message中:
Message& Message::operator=(const Folder &f)
{
remove_from_Msgs();// 从每个Message中删除此Folder
msgs = f.msgs;// 从右侧运算对象拷贝Message集合
add_to_Messages(f);// 将此Folders添加到每个新Message中
return *this;
}
整体代码:
#ifndef MESSSAGE13_36_H
#define MESSSAGE13_36_H
#include <set>
#include <string>
using std::set;
using std::string;
class Folder;
class Message
{
friend void swap(Message&, Message&);
friend class Folder;
public:
//folders被隐式初始化为空集合
explicit Message(const string &str = "")
:contents(str)
{
}
//拷贝控制成员,用来管理指向本Message的指针
Message(const Message&);//拷贝构造函数
Message& operator=(const Message&);//拷贝赋值运算符
~Message();//析构函数
//从给定Folder集合中添加/删除本Message
void save(Folder&);
void remove(Folder&);
void debug_print();
private:
string contents;//实际消息文本
set<Folder *> folders;//包含本Message的Folder
//拷贝构造函数,拷贝赋值运算符和析构函数所使用的工具函数
//将本Message添加到指向参数的Folder中
void add_to_Folders(const Message&);
//从folders中的每个Folder中删除本Message
void remove_from_Folders();
//used by Folder class to add self to this Message's set of Folder's
void addFldr(Folder *f)
{
folders.insert(f);
}
void remFldr(Folder *f)
{
folders.erase(f);
}
};
// declaration for swap should be in the same header as Message itself
void swap(Message&, Message&);
class Folder
{
friend void swap(Message&, Message&);
friend class Message;
public:
~Folder();// remove self from Messages in msgs
Folder(const Folder&);// add new folder to each Message in msgs
Folder& operator=(const Folder&);// delete Folder from lhs messages, add Folder to rhs messages
Folder() = default;//默认构造函数
void save(Message&);// add this message to folder
void remove(Message&);// remove this message from this folder
void debug_print();// print contents and it's list of Folders
private:
set<Message*> msgs;// messages in this folder
void add_to_Messages(const Folder&);// add this Folder to each Message
void remove_from_Msgs();// remove this Folder from each Message
void addMsg(Message *m)
{
msgs.insert(m);
}
//删除一条消息
void remMsg(Message *m)
{
msgs.erase(m);
}
};
#endif // MESSSAGE13_36_H
#include "Messsage13_36.h"
#include <iostream>
#include <vector>
#include <utility>
using std::cerr;
using std::endl;
using std::cout;
using std::vector;
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的指针添加到它的(新)jFolder中
for(auto f: lhs.folders)
f->addMsg(&lhs);
for(auto f: rhs.folders)
f->addMsg(&rhs);
}
Message::Message(const Message &m)
:contents(m.contents), folders(m.folders)
{
add_to_Folders(m);// add this Message to the Folders that point to m
}
Message& Message::operator=(const Message &rhs)
{
// handle self-assignment by removing pointers before inserting them
remove_from_Folders();// update existing Folders
contents = rhs.contents;// copy message contents from rhs
folders = rhs.folders;// copy Folder pointers from rhs
add_to_Folders(rhs);// add this Message to those Folders
return *this;
}
Message::~Message()
{
remove_from_Folders();
}
void Message::add_to_Folders(const Message &m)
{
for(auto f: m.folders)
f->addMsg(this);
}
void Message::remove_from_Folders()
{
for(auto f: folders)
f->remMsg(this);
folders.clear();
}
void Folder::add_to_Messages(const Folder &f)
{
for(auto msg: f.msgs)
msg->addFldr(this);
}
Folder::Folder(const Folder &f)
: msgs(f.msgs)
{
add_to_Messages(f);
}
Folder& Folder::operator=(const Folder &f)
{
remove_from_Msgs();
msgs = f.msgs;
add_to_Messages(f);
return *this;
}
Folder::~Folder()
{
remove_from_Msgs();
}
void Folder::remove_from_Msgs()
{
while(!msgs.empty())
(*msgs.begin())->remove(*this);
}
void Message::save(Folder &f)
{
folders.insert(&f);
f.addMsg(this);
}
void Message::remove(Folder &f)
{
folders.erase(&f);
f.remMsg(this);
}
void Folder::save(Message &m)
{
msgs.insert(&m);
m.addFldr(this);
}
void Folder::remove(Message &m)
{
msgs.erase(&m);
m.remFldr(this);
}
void Folder::debug_print()
{
cerr << "Folder contains " << msgs.size() << " message" << endl;
int ctr = 1;
for(auto m: msgs)
{
cerr << "Message " << ctr++ << ":\t" << m->contents << endl;
}
}
void Message::debug_print()
{
cerr << "Message:\t" << contents << endl;
cerr << "Appears in " << folders.size() << " Folders" << endl;
}
int main(int argc, const char * argv[])
{
string s1("contents1");
string s2("contents2");
string s3("contents3");
string s4("contents4");
string s5("contents5");
string s6("contents6");
Message m1(s1);
Message m2(s2);
Message m3(s3);
Message m4(s4);
Message m5(s5);
Message m6(s6);
Folder f1;
Folder f2;
m1.save(f1);
m3.save(f1);
m5.save(f1);
m1.save(f2);
m2.save(f2);
m4.save(f2);
m6.save(f2);
m1.debug_print();
f2.debug_print();
cout << "create some copies==============\n" << endl;
Message c1(m1);
Message c2(m2), c4(m4), c6(m6);
m1.debug_print();
f2.debug_print();
cout << "now some assignments==============\n" << endl;
m2 = m3;
m4 = m5;
m6 = m3;
m1 = m5;
m1.debug_print();
f2.debug_print();
cout << "finally, self-assignment==============\n" << endl;
m2 = m2;
m1 = m1;
m1.debug_print();
f2.debug_print();
cout << "vector==============\n" << endl;
vector<Message> vm;
cout << "capacity: " << vm.capacity() << endl;
vm.push_back(m1);
cout << "capacity: " << vm.capacity() << endl;
vm.push_back(m2);
cout << "capacity: " << vm.capacity() << endl;
vm.push_back(m3);
cout << "capacity: " << vm.capacity() << endl;
vm.push_back(m4);
cout << "capacity: " << vm.capacity() << endl;
vm.push_back(m5);
cout << "capacity: " << vm.capacity() << endl;
vm.push_back(m6);
cout << "vector=======Folder=======\n" << endl;
vector<Folder> vf;
cout << "capacity: " << vf.capacity() << endl;
vf.push_back(f1);
cout << "capacity: " << vf.capacity() << endl;
vf.push_back(f2);
cout << "vector=======Folder=2======\n" << endl;
cout << "capacity: " << vf.capacity() << endl;
vf.push_back(Folder(f1));
cout << "capacity: " << vf.capacity() << endl;
vf.push_back(Folder(f2));
cout << "capacity: " << vf.capacity() << endl;
vf.push_back(Folder());
Folder f3;
f3.save(m6);
cout << "capacity: " << vf.capacity() << endl;
vf.push_back(f3);
std::cout << "Hello, World!\n";
return 0;
}
运行结果:
练习13.37:为Message类添加成员,实现向folders添加或删除一个给定的Folder*。这两个成员类似Folder类的addMsg和remMsg操作。
【出题思路】
本题练习复杂类结构中一些辅助函数的定义。
【解答】
上一题已经定义了addFldr,可类似定义remFldr,如下所示:
void remFldr(Folder *f) { folders.erase(f);}
练习13.38:我们并未使用拷贝并交换方式来设计Message的赋值运算符。你认为其原因是什么?
【出题思路】
深入理解这类簿记操作问题中拷贝控制成员的作用。
【解答】
如果采用拷贝并交换方式,执行过程是这样的:
1.由于赋值运算符的参数是Message类型,因此会将实参拷贝给形参rhs,这会触发拷贝构造函数,将实参的contents和folders拷贝给rhs,并调用add_to_Folders将rhs添加到folders的所有文件夹中。
2.随后赋值运算符调用swap交换*this和rhs,首先遍历两者的folders,将它们从自己的文件夹中删除;然后调用string和set的swap交换它们的contents和folders;最后,再遍历两者新的folders,将它们分别添加到自己的新文件夹中。
3.最后,赋值运算符结束,rhs被销毁,析构函数调用remove_from_Folders将rhs从自己的所有文件夹中删除。
显然,语义是正确的,达到了预期目的。但效率低下,rhs创建、销毁并两次添加、删除是无意义的。而采用拷贝赋值运算符的标准编写方式,形参rhs为引用类型,就能避免这些冗余操作,具有更好的性能。