关于C++拷贝控制

这篇具有很好参考价值的文章主要介绍了关于C++拷贝控制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

通常来说,对于类内动态分配资源的类需要进行拷贝控制:要在拷贝构造函数、拷贝赋值运算符、析构函数中实现安全高效的操作来管理内存。但是资源管理并不是一个类需要定义自己的拷贝控制成员的唯一原因。C++ Primer 第5版 中给出了一个Message类与Folder类的例子,分别表示电子邮件消息和消息目录。每个Message可以出现在多个Folder中,但是,任意给定的Message的内容只有一个副本。如果一条Message的内容被改变,我们从任意的Folder中看到的该Message都是改变后的版本。为了记录Message位于哪些Folder中,每个Message都用一个set保存所在的Folder的指针,同样的,每个Folder都用一个set保存它包含的Message的指针。二者的设计如下图所示:

关于C++拷贝控制

C++ Primer中并没有给出Folder类的实现。在对Message及Folder类的复现过程中,出现了一个问题,导致了严重错误。

Message及Folder类的初步设计如下:

Message类:

class Message
{
    friend class Folder;
private:
    string contents;
    set<Folder*> folders;

    //工具函数:在本消息的folders列表中加入/删除新文件夹指针f
    void addFolder(Folder* f);
    void remFolder(Folder* f);

    //工具函数:在本消息folders列表中的所有Folder中删除指向此消息的指针
    void remove_from_folders();

public:
    string getContents();
    set<Folder*> getFolders();

    //构造函数与拷贝控制
    Message(const string& s = " ") :contents(s) {};
    ~Message();

    //接口:将本消息存入给定文件夹f
    void save(Folder& f);
    //接口:将本消息在给定文件夹中删除
    void remove(Folder& f);
};

Folder类:

class Folder
{
    friend class Message;
private:
    set<Message*> messages;

    //工具函数:将给定消息的指针添加到本文件夹的messages中
    void addMsg(Message* m);
    //工具函数:将给定消息的指针在本文件夹中的messages中删除
    void remMsg(Message* m);

public:
    set<Message*> getMessages();
};

这两个类有对称的功能函数:Message.addFolder(Folder* f)与Folder.addMsg(Message* m),以及Message.remFolder(Folder* f)与Folder.remMsg(Message* m),用来实现Message的保存以及拷贝控制操作等。

所有成员函数的实现如下:

string Message::getContents()
{
    return contents;
}
set<Folder*> Message::getFolders()
{
    return folders;
}

void Message::addFolder(Folder* f)
{
    this->folders.insert(f);
}
void Message::remFolder(Folder* f)
{
    this->folders.erase(f);
}

//接口:将本消息存入给定文件夹f
void Message::save(Folder& f)
{
    this->addFolder(&f);
    f.addMsg(this);
}
//接口:将本消息在给定文件夹中删除
void Message::remove(Folder& f)
{
    this->remFolder(&f);
    f.remMsg(this);
}

void Message::remove_from_folders()
{
    for (auto f : folders)
    {
        f->remMsg(this);
    }
}

Message::~Message()
{
    remove_from_folders();
}

/*Folder的成员函数*/
//工具函数:将给定消息的指针添加到本文件夹的messages中
void Folder::addMsg(Message* m)
{
    messages.insert(m);
}
//工具函数:将给定消息的指针在本文件夹中的messages中删除
void Folder::remMsg(Message* m)
{
    messages.erase(m);
}

set<Message*> Folder::getMessages()
{
    return messages;
}

 在这个实现版本的代码测试中,出现了这样一个问题:程序会有运行时错误,主函数的返回值不为0。测试代码如下:

void test()
{
    Message m1("Hello,"), m2("World"), m3("!");
    Folder f1, f2;
    m1.save(f1); m1.save(f2);
    m2.save(f2);
    m3.save(f2);
    m2.remove(f2);
}

int main()
{
    test();
    system("pause");
    return 0;
}

运行结果:

关于C++拷贝控制

 经调试排查原因之后,找到了问题所在:试图对已经被销毁了的对象的指针进行解引用。该bug和“函数返回指向局部变量的指针”所导致的问题类似。我们为Message类定义了析构函数:

Message::~Message()
{
    remove_from_folders();
}

这个析构函数的实现与C++ Primer上的实现完全一致。该析构函数意图在于当一个Message被销毁时,应该清除它的folders中的所有指向它的指针。这看上去合理,可是在这里却导致了内存错误。原因在于,remove_from_folders()操作会访问该Message所在的所有Folder的指针,而若这些Folder的销毁在该Message的销毁之前进行,则操作会试图通过指针解引用,来访问已被销毁的Folder对象。这会导致严重的运行时错误。在本例中,局部变量Folder f1的创建在m1之后,将m1加入f1,test()函数结束时,按照局部变量的销毁顺序,会先销毁后创建的对象f1,于是,m1的析构函数会试图解引用已被销毁对象f1的指针。出现这个问题,是因为在实现的时候没有按照C++ Primer上的设计正确地实现Folder的析构函数。我们按照如下实现Folder的析构函数:

class Folder
{
    /*其他Folder的声明不变*/

    /*加入Folder的析构函数,以及一个工具函数,对于将要销毁的Folder,这个工具函数负责删除该Folder中所有Message指向它的指针*/
private:  
    void remove_from_messages();
public:    
    ~Folder();
};

void Folder::remove_from_messages()
{
    for (auto m : messages)
        m->remFolder(this);
}

Folder::~Folder()
{
    remove_from_messages();
}

此时,Folder的析构函数在Folder被销毁时可以正确地删除所有Message中指向自身的指针,就避免了对已经销毁的对象进行解引用的操作。反过来,若先定义的是f1,后定义的是m1,在m1先销毁时,m1的析构函数也可以正确地删除所有Folder中指向m1的指针。所以,无论Folder先被销毁,还是Message先被销毁,都能够正确地执行析构操作。使用与上面同样的test()函数进行测试,程序可以正常地退出了:

关于C++拷贝控制

这个例子也给了我们又一次提醒:在C++中,指针与拷贝控制、内存管理一定要万分小心谨慎,一点小的差错也可能导致程序的灾难。文章来源地址https://www.toymoban.com/news/detail-710540.html

到了这里,关于关于C++拷贝控制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 海关数据有对于外贸企业来说有什么作用?要如何使用才会有效果

    海关数据作为传统四大客户开发途径之一,一直以来知道的外贸人都很多,用的也很多,那么为什么这么多外贸人喜欢用海关数据呢,海关数据有对于外贸企业来说有什么作用?要如何使用才会有效果,如何购买海关数据 1.快速找到适合自己的买家  海关提单是买卖双方的交

    2023年04月26日
    浏览(46)
  • 【区块链】走进web3的世界-对于前端来说,web2与web3的区别

    web3离不开几个概念,智能合约、区块链、前端交互     1、智能合约可以直接与区块链中的区块进行交互;     2、前端通过web3.js/ethers.js等npm库可以和智能合约进行交互; 说的直白点,web3与web2对于前端来说,只是对接的对象发生了变化,从后端API接口改为了智能合约。这

    2023年04月16日
    浏览(46)
  • C++(13):拷贝控制

    一个类通过定义五种特殊的成员函数来控制这些操作,包括: 拷贝构造函数 (copy constructor)、 拷贝赋值运算符 (copy-assignment operator)、 移动构造函数 (moveconstructor)、 移动赋值运算符 (move-assignment operator)和 析构函数 (destructor)。 拷贝和移动构造函数定义了当用同类型的另一

    2024年02月16日
    浏览(25)
  • 在C++中控制调试信息的输出通常通过预处理指令(如 #define)和条件编译指令(如 #ifdef、#ifndef、#endif)来实现。

    在C++中,控制调试信息的输出通常通过预处理指令(如 #define )和条件编译指令(如 #ifdef 、 #ifndef 、 #endif )来实现。这种方法提供了一种灵活的方式来包含或排除调试代码,而无需对代码本身进行大量修改。以下是实现这一功能的一种常见方法: 定义一个宏用于控制调试

    2024年02月02日
    浏览(39)
  • 《c++ primer笔记》第十三章 拷贝控制

    1.1拷贝构造函数 ​ 如果一个构造函数的第一个参数是自身类类型的 引用 ,且任何额外参数都由默认值,则此构造函数成为拷贝构造函数。 拷贝构造函数在某些情况下会被隐式地使用,所以不能定义为 expicit 。 合成拷贝构造函数 ​ 合成某某函数 一般出现在我们没定义该函

    2023年04月25日
    浏览(46)
  • C++中main函数如何调用类内函数

    以力扣209题为例 将类内函数设置为静态static,就可以直接调用了,另外,在调用时要加上类名。 另外,补充:在C++中,“::”表示作用域,::前面是类名,后面是该类的成员名称,C++为避免不同的类有名称相同的成员而采用作用域的方式进行区分。

    2024年02月11日
    浏览(45)
  • MySQL8.0版本在CentOS系统安装&&修改MySQL的root密码和允许root远程登录(介绍但对于生产来说不安全,学习可用)

    注意:安装操作 需要root权限 安装 配置yum仓库(秘钥为GnuPG签名,2023为版本的秘钥(对我当前是最新的),后面可能会改变)  # 更新密钥 rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql- 2023 # 安装Mysql8.x版本 yum库, 其中的el7代表的应该是LInux7版本,可以参考阿里云的# MySQL :: 下载

    2024年04月14日
    浏览(52)
  • C++类和对象终章——友元函数 | 友元类 | 内部类 | 匿名对象 | 关于拷贝对象时一些编译器优化

    🌸作者简介: 花想云 ,在读本科生一枚,致力于 C/C++、Linux 学习。 🌸 本文收录于 C++系列 ,本专栏主要内容为 C++ 初阶、C++ 进阶、STL 详解等,专为大学生打造全套 C++ 学习教程,持续更新! 🌸 相关专栏推荐: C语言初阶系列 、 C语言进阶系列 、 数据结构与算法 、 Linu

    2023年04月15日
    浏览(41)
  • Python数据权限的管理通常涉及到几个关键组件:身份验证,、授权和访问控制。这通常是通过使用数据库、ORM(对象关系映射)框架、API框架和中间件

    在Python中,数据权限的管理通常涉及到几个关键组件:身份验证,、授权和访问控制。这通常是通过使用数据库、ORM(对象关系映射)框架、API框架和中间件等技术来实现的。以下是一些建议的步骤和工具,用于在Python项目中实施数据权限管理: 用户身份验证: 使用如Djan

    2024年04月26日
    浏览(43)
  • 发布关于PostGIS对于USD格式的拓展

    我们非常高兴的发布为了一年一度的SIGGRAPH 2023发布关于为PostGIS支持USD格式的新拓展。 新添加了3个函数 函数ST_AsUSDA和ST_AsUSDC用于转换PostGIS的geometry为USD数据,可以是文本格式的USDA用于调试,或者是紧凑的二进制USDC格式。所有的参数解释如下。 geom, 输入PostGIS几何体 usd_root

    2024年02月14日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包