make c+++ 未定义的引用_Item21 优先使用std::make_unique和std::make_shared来代替new

2023-05-16

    std::make_shared是在 C++11中添加的一个专门用来创建智能指针的方法,而不幸的是 std::make_uniqueC++11中并没有,直到 C++14才引进来。不过实现它也是一件很容易的一件事,如下:

bad030b86b01afa9dec20a859c657b2b.png

   上面的这个版本是不支持创建数组类型的智能指针,也不支持自定义删除器的。尽管如此,当你需要 make_unique的时候,你也可以很简单的实现一个简易版本。

   还记得我们在之前是怎么创建智能指针的吗?, new一个原始指针,然后用它初始化一个智能指针类型的变量即可,那么既然已经有了创建智能指针的方法,本文为何又要引入第二种方法呢?因为使用make系列函数创建智能指针要比原始方法创建有诸多优点,因此在实际使用过程中应该优先使用make系列函数来创建智能指针。那么到底有哪些优点呢,本文将会一一讲解。

  • 避免重复,代码更清晰,不容易引入不一致的代码导致bug。

a20fc056ec03527c6c503199058b8ea8.png

   上面的代码在使用 new创建智能指针的时候,你会发现类型出现了重复(出现了两次 Widget),后面如果要修改类型的话,必须要全部修改,否则会导致代码的不一致,而使用 make系列函数创建的智能指针就没有这个问题。

  • 异常安全

bda5c64214c8bd2b1b4f68879cb10880.png

上面这行代码总共分成下面几个步骤:

  1. newWidget

  2. std::shared_ptr<Widget>构造函数

  3. computePriority

但是不幸的是这三行代码的执行顺序是未定义的,也就是什么样的顺序都可以,如果按照下面的顺序执行,那么就有可能导致资源泄漏。

  1. newWidget

  2. computePriority

  3. std::shared_ptr<Widget>

当步骤2的 computePriority发生异常,这会导致步骤1分配的内存无法得到回收,如果在这里使用make系列函数创建的话就不会出现这种问题。

35f949257977fece1fefe76a81963b3b.png

  • 避免多次内存分配,提升性能

       还记得在上一节中谈到的 shared_ptr控制块的概念吗?,总的来说一个智能指针包含了两个部分一个部分是分配的堆内存,另外一个部分就是控制块。通常情况下我们使用下面的方法创建智能指针的时候,其实就进行两次内存分配的操作,一次是分配对象的内存,另外一次则是分配控制块所需要的内存。

如果是使用 make_shared就不会存在这个问题, make_shared会一次性分配对象和控制块所需要的内存。

   上文中谈到了诸多 make_shared的优点和好处,直觉上我们就应该无论如何都使用 make_shared来创建智能指针,这个时候我不得不站出来谈谈 make_shared的缺点,避免给大家造成一定要使用 make_shared的错觉,正如本文标题一样,应该是优先使用,只有清楚的认识到 make_shared的缺点才能更好的使用 make_shared

  • 无法自定义删除器

       第一个缺点很明显那就是在使用make_shared创建的智能指针的时候我们是没办法自定义删除器的,这是一个很遗憾的地方,我觉得在 C++17可能会解决这个问题。

  • 使用 make_shared会带来语法上的歧义

e6b17b4dc06569f6189f828020fda190.png

上面的代码是什么含义呢?,创建10个元素,每个元素的值是20,还是创建两个元素分别是10和20。对应于下面的代码:

38635b2c7fb67405106bf0215afb2bcf.png

   区别就在于使用的是括号,还是花括号。好在 make_shared使用的是括号的语义,那如何实现第二种语义呢?很可惜 make_shared做不到。这个时候只能使用 new的方式来创建。

  • 最后一个缺点但也是最重要的一个缺点,延长了对象销毁的时机

       智能指针在引用计数变为0的时候会进行对象的析构,但是如果你使用了 make_shared创建智能指针的话,即使引用计数变为0对象也不会析构,这一切的根本原因就是 std::weak_ptr,为了能让 weak_ptr可以探查对象的生死,在智能指针的控制块中会保存 weak_ptr相关的信息,以便 weak_ptr可以探查对象的生死,就是因为这个原因导致引用计数变为0的时候,对象是析构了,但是控制块仍然存在,直到所有的 weak_ptr都销亡了,控制块才会释放。那么问题就来了,使用 make_shared创建的智能指针其控制块和对象所占用的内存是放在一起的,只能一起释放,无法释放其中的一部分。这也导致了对象的生命周期被拉长,直到所有的 weak_ptr都析构为止。

   谈完了优缺点或许你开始迷惑,究竟该如何在两种方式中选择适合自己的,不希望对象的生命周期被拉长,又不希望有异常安全的问题。对于异常安全的问题来说其实很好规避。

f52155ff8ac51358134bad604f8c25b7.png

   将创建智能指针的步骤独立起来,这样就不存在异常安全的问题了,但是上面的方法还有另外一个问题就是,会造成一点点性能上的问题。会造成智能指针拷贝的开销。std::shared_ptr<Widget>(newWidget)

   这种写法的情况下,这是一个右值,默认会进行移动,这样就避免了拷贝,如果改成上面的方式, spw 是一个左值那么就必须进行拷贝复制了,为此可以将其转换为右值进程移动,避免这次拷贝。

b34b5d1d871b19548170de62653f35e1.png

到此为止,一个异常安全的,性能上也没有什么损失的方法就有了。

Tips

  1. 相比于直接使用new,make系列的函数消除了源代码重复、提升了异常安全性,并且 std::make_shared和 std::allocate_shared生成的代码更小更快

  2. 对于希望自定义删除器以及通过{}进行初始值的设定时,不适合使用make系列函数

  3. 对于 std::shared_ptr来说有两类场景不适合使用 make系列函数,第一个就是需要自定义管理内存的,第二个就是管理大对象时,并且存在 std::weak_ptr比 std::shared_ptr生命周期更长的情况

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

make c+++ 未定义的引用_Item21 优先使用std::make_unique和std::make_shared来代替new 的相关文章

  • 为什么 std::type_info 不可复制?我可以把它存放在某个地方吗?

    The std type info类是不可复制的 这使得很难将其存储在对象中以供以后使用 我应该怎么办 C 11 中有一个更好的解决方案 一个名为 std type index 的新可复制包装器 您需要包含标头 typeindex 才能使用
  • 如果成功或失败,如何返回 std::copy 的值?

    我在用std copy将对象复制到std deque到一个文件 代码工作正常 但我需要检查复制是否成功 因此我需要设置标志 否则抛出异常 我已经用谷歌搜索但找不到解决方案如何检查是否std copy已成功将值复制到文件中 有人可以照亮它吗
  • 在应用程序和 Web 之间共享数据库

    我参与了一个必须从现有数据库检索查询的应用程序 该数据库必须在远程服务器中的 php 中实现 并且将是管理员必须将内容插入共享数据库的地方 但我不知道如何正确实现或什么是最佳解决方案 我正在考虑使用 php 创建一个 sqlite 数据库并
  • 关于使用 iostream 进行解析的准则是什么?

    我发现自己最近写了很多解析代码 大部分是自定义格式 但并不真正相关 为了增强可重用性 我选择将解析函数基于 I O 流 以便我可以将它们与诸如boost lexical cast lt gt 然而 我意识到我从未在任何地方读过有关如何正确执
  • 生成指定数量的随机但独特的颜色

    我想做的是给 listView 中的每个项目赋予唯一的颜色 所以我的列表视图中的项目数为 计数 我的方法是调用下面的方法并给出我的项目的方法编号 然后它应该有一个保存第一种颜色的数组 然后当要生成下一个颜色时 应该将其与数组中之前的颜色进行
  • C++ TR2 文件系统库的状态如何?

    截至上次布里斯托尔会议 C TR2 文件系统库的状态如何 它将成为 C 1Y C 14 的一部分 还是暂停 或者最近三次会议有任何已知的评论吗 It has 最近获得ISO委员会一致批准 http article gmane org gma
  • 从 boost::shared_ptr 转换为 std::shared_ptr?

    我有一个内部使用 Boost 版本的库shared ptr并且只暴露那些 对于我的应用程序 我想使用std shared ptr只要有可能 遗憾的是 这两种类型之间没有直接转换 因为引用计数的内容取决于实现 有什么办法可以同时拥有boost
  • 在 Linux 上获取机器 ID 的最佳方法?

    获取唯一机器 ID 的最佳实践方法是什么GNU Linux for i386建筑学 除了这个还有什么好的办法吗mac地址 根据您的内核 DMI 信息可能可以通过 sysfs 获得 尝试一下 cat sys class dmi id boar
  • 为什么我不能在 C++ 中的 `std::map` 中存储引用?

    我知道引用不是指针 而是对象的别名 但是 我仍然不明白这对我作为程序员到底意味着什么 即幕后的引用是什么 我认为理解这一点的最好方法是理解为什么我无法在地图中存储参考 我知道我需要停止将引用视为指针的语法糖 只是不知道如何 按照我的理解 引
  • 我需要关闭 std::fstream 吗? [复制]

    这个问题在这里已经有答案了 可能的重复 我需要手动关闭 ifstream 吗 https stackoverflow com questions 748014 do i need to manually close a ifstream 我
  • 为什么unique_ptr::~unique_ptr需要T的定义?

    如果我有一堂 酒吧 课 bar h class Bar public Bar 我转发声明与另一个类 Foo 中的 std unique ptr 一起使用 foo h include
  • 如何在 C++ 中前向声明 std::set?

    为了加快编译过程 我正在尝试简化我的头文件MyClass hpp通过前向声明 STL 容器 例如 std vector std set But std set can NOT在以下代码中进行前向声明 同时std vector can be
  • 值类型不完整的映射

    我收到以下错误 class Test std map
  • Postgresql 强制执行唯一的双向列组合

    我正在尝试创建一个表 该表将在两个方向上强制执行相同类型的两列的唯一组合 例如 这是非法的 col1 col2 1 2 2 1 我已经想出了这个 但它不起作用 database gt d friend Table public friend
  • 在多个不同线程之间共享变量

    我想在多个线程之间共享一个变量 如下所示 boolean flag true T1 main new T1 T2 help new T2 main start help start 我想分享flag在主线程和帮助线程之间 这是我创建的两个不
  • NFC标签唯一ID

    我正在开发一个包括 NFC 标签和 Android 手机的系统 使用 NFC 标签的唯一 ID 但不知道4种NFC标签之间有什么区别 我发现了这个 兼容 NFC 的标签可以采用以下技术 标准 他们每个人都有不同的 ID 概念 NFC Tag
  • c++11 中的 std::thread 问题

    我在尝试从标准模板库编译具有多线程的程序时遇到一些麻烦 当我尝试编译以下程序时 它返回一个晦涩的错误 include
  • 在 C++ 中通过引用传递 std 算法谓词

    我正在尝试从 a 中删除元素std list并保留已删除元素的一些统计信息 为此 我使用列表中的remove if 函数 并且我有一个谓词 我想使用这个谓词来收集统计数据 这是谓词的代码 class TestPredicate privat
  • 从空缓冲区构造“std::ostream”是否有效?

    考虑以下 std ostream out nullptr 这是合法且明确定义的吗 如果我现在这样做怎么样 out lt lt hello world n 这是合法且明确定义的吗 如果是这样 大概这是一种无操作 是的 实例化该流是合法且定义明
  • std::bind 重载解析

    下面的代码工作正常 include

随机推荐