我什么时候想要从原始指针

时间:2015-07-24 08:11:28

标签: c++ c++11 shared-ptr

感谢std::make_shared,我想知道,带有原始指针的std::shared_ptr构造函数是否具有任何值,除非与传统/库代码接口时,例如存储工厂的输出时。

  • 还有其他合法用例吗?
  • 避免使用构造函数是否合理?
  • 即使是在对程序员进行代码检查的范围内,也会在程序员使用它时发出警告?
  • 相同的指南(无论它们是什么)是否适用于shared_ptr<T>::reset(T*)

关于代码检查: 我知道与遗留/库代码的接口很常见,因此自动代码检查可能会有问题,但在目前为止我遇到的大多数情况下,我反而使用unique_ptr而且我还是也没有谈论在-Wall弹出的编译器警告,而是关于代码审查期间静态代码分析的规则。

我的动机:
相对容易说&#34;不要使用std::shared_ptr<T>(new T(...)),总是更喜欢std::make_shared<T>(...)&#34; (我认为这是正确的建议?)。但是我想知道它是否一般都没有设计气味,如果必须从原始指针创建shared_ptr,甚至 - 或者特别是 - 如果对象不是仅通过{{创建的话1}},因为该对象应该被创建为&#34;共享&#34;或&#34;独特的&#34;首先是对象。

6 个答案:

答案 0 :(得分:14)

首先出现的用例是删除器不是默认值delete

E.g。在Windows环境中,必须有时使用COM对象,必须通过Release在对象本身上完成这些对象的释放。当然可以使用ATL,但不是每个人都想使用它。

struct ReleaseCom {
  template <class T>
  void operator() (T* p) const
  {
    p->Release();
  }
};
IComInterface* p = // co created or returned as a result
std::share_ptr<IComInterface> sp(p, ReleaseCom());

一个更不常见的情况 - 但仍然有效 - 是在dll,OS或库中自定义分配对象(句柄或甚至原始内存)并且具有必须调用的自己的关联清理函数(可能会也可能不会依次致电delete。如果涉及内存分配std::allocate_shared提供了对使用的分配器的更多增强控制,而不暴露原始指针。

我个人的感觉是,给定std::make_sharedstd::allocate_shared,从原始指针构造shared_ptr的要求越来越少。即使是上面的情况,也可以包含在公用事业分配和管理功能中,从主业务逻辑代码中删除。

答案 1 :(得分:9)

Scott Meyers在Effective Modern C++

中列出了几个例外情况

第一个已被提及。如果您需要提供自定义删除工具,则无法使用make_shared

第二个是如果你在一个有内存问题的系统上并且你正在分配一个非常大的对象,那么使用make_shared为对象和控制块分配一个块,包括引用计数。如果您有任何weak_ptr指向该对象,则无法释放内存。另一方面,如果您没有使用make_shared,那么只要删除最后一个shared_ptr,就可以释放非常大的对象的内存。

答案 2 :(得分:5)

  

•是否还有其他合法用例?

是:资源没有映射到新/删除的情况:

handle_type APIXCreateHandle(); // third party lib
void APIXDestroyHandle(handle_type h); // third party lib

在这种情况下,您将需要直接使用构造函数。

  

•避免使用构造函数是否合理?

当功能与make_shared重叠时,是。

  

•相同的指南(无论它们是什么)是否适用于shared_ptr :: reset(T *)?

我认为他们不应该这样做。如果您真的想要,可以使用带有std :: make_shared调用结果的赋值替换重置调用。我更愿意看到重置调用 - 它意图更明确。

  

关于代码检查:我知道与遗留/库代码的接口很常见

考虑将第三方库包装到一个接口中,以确保返回的值是unique_ptr-wrapped。这将为您提供集中点和其他便利/安全优化的机会。

  

相对容易说[...](我相信这是正确的建议?)。但是我想知道它是不是一般的设计气味

使用它不是设计气味;只有当std :: make_shared / std :: make_unique 同样适用时才能使用它。

编辑:解决问题的重点:您可能无法为此添加静态分析规则,除非您还向其添加约定/例外列表(即& #34;除自定义删除器和第三方lib API适配层外,应始终使用make_shared和make_unique&#34;)。对于某些文件,可能会跳过这样的规则。

当您直接使用构造函数时,将它们放在专用于此的函数中可能是个好主意(类似于make_unique和make_shared所做的那样):

namespace api_x
{
    std::shared_ptr<handle_type> make_handle(); // calls APIXCreateHandle internally
                                                // also calls the constructor
                                                // to std::shared_ptr
}

答案 3 :(得分:3)

尽可能使用std::make_shared

有两个原因你可能无法逃脱:

  • 您想为对象使用自定义删除器。如果您使用不使用RAII的代码进行交互,则可能就是这种情况。其他答案提供了更多细节。

  • 您有一个要调用的自定义新运算符。 std::make_shared无法为您调用它,因为它的重点是分配内存一次(虽然标准不要求)。请参阅http://ideone.com/HjmFl1

std::make_shared背后的原因并不是你避免使用new关键字,而是避免了内存分配。共享指针需要设置一些控制数据结构。因此,在设置shared_ptr而不是make_shared时,您必须进行两次分配:一个用于对象,另一个用于控制结构。 make_shared(通常)只分配一个(更大的)内存块,并构造包含的对象和控制结构。 make_shared背后的原因(最初是)在大多数情况下它更有效,而不是语法更漂亮。这也是C ++ 11中没有std::make_unique的原因。 (正如ChrisDrew指出的那样,我建议std::make_unique仅用于对称/更漂亮的语法。它可以帮助以更紧凑的方式编写异常安全代码,但请参阅Herb Sutter's GotW #102this question 。)

答案 4 :(得分:3)

除了其他答案之外,如果构造函数是私有的,则不能使用make_shared,例如来自工厂函数。

class C
{
public:
    static std::shared_ptr<C> create()
    {
        // fails
        // return std::make_shared<C>();

        return std::shared_ptr<C>(new C);
    }
private:
    C();
};

答案 5 :(得分:0)

许多答案提供至少一个独特的方面,所以我决定做一个总结答案。归功于@Niall,@ Chris Drew,@ utnapistim,@ isanae,@ Markus Mayr和(代理人)Scott Meyers。

原因,您可能不希望/可能无法使用<template is="dom-bind" id="app"> ... <iron-list items="[[data]]" as="item"> <template> <div> <div class="item"> ... <a href$="{{_getProject(item.project_id)}}">...</a> </div> </div> </template> </iron-list> </template> <script> var app = document.getElementById('app'); app._getProject = function (url) { return 'webservices/api/projects/' + url } </script> 的原因是:

  • 您必须使用自定义删除器和/或甚至自定义分配器/新运算符 这可能是最常见的原因,尤其是在与第三方库连接时。 std::allocate_shared可能会在某些情况下有所帮助。
  • 如果记忆是一个问题,你经常有弱指针比对象寿命长。
    由于托管对象是在与控制块相同的内存块上创建的,因此在最后一个弱指针被破坏之前,不能释放内存。
  • 如果您有私人构造函数,例如只有公共工厂的功能。
    在这种情况下,让make_shared成为朋友并不是一个可行的解决方案,因为实际构造可能会发生在某个辅助函数/类中。

关于代码检查,上面列出的例外可能太多,无法自动检查“尽可能使用make_shared指南,以及调用违反该指南的行为指导设计气味本身。

作为一个亮点,即使一个库需要自定义分配器和解除分配器功能,我们也不能/不想使用make_shared,我们可以让我们自己的make版本共享至少封装了分配和删除调用并增加了异常安全性(尽管它很可能不提供单一的分配优势)。