《C++ Primer》第13章 13.4节习题答案

本博客详细介绍了C++Primer第13章关于拷贝控制的示例习题解答,包括Message和Folder类的设计。重点讨论了拷贝构造函数、拷贝赋值运算符和析构函数在实现簿记工作中的作用,以及为何需要自定义这些成员以确保数据一致性。同时,解释了为何Message成员save和remove参数为Folder引用而非指针或const引用的原因。
摘要由CSDN通过智能技术生成

《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为引用类型,就能避免这些冗余操作,具有更好的性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值