繁体   English   中英

我什么时候想从原始指针构造共享指针

[英]When would I want to construct a shared pointer from a raw pointer

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

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

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


我的动机:
可以相对容易地说“不要使用std::shared_ptr<T>(new T(...)) ,总是更喜欢std::make_shared<T>(...) (我相信这是正确的建议?)。 但是我想知道它是不是一般的设计气味,如果必须从原始指针创建一个shared_ptr ,甚至 - 或者特别是 - 如果该对象不是刚刚通过new创建的,因为该对象应该已经创建或者首先作为“共享”或“独特”对象。

弹出的第一个用例是当删除器不是默认delete

例如,在Windows环境中,必须有时使用COM对象,必须通过Release在对象本身上完成这些对象的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的要求越来越少。 即使是上面的情况,也可以包含在公用事业分配和管理功能中,从主业务逻辑代码中删除。

Scott Meyers列出了Effective Modern C ++中的几个例外

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

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

•是否有其他合法用例?

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

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 也能正常工作时才使用它。

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

当您直接使用构造函数时,将它们放在专用于此的函数中可能是个好主意(类似于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
}

尽可能使用std::make_shared

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

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

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

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

除了其他答案之外,如果构造函数是私有的,则不能使用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();
};

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

您可能不希望/可能无法使用make_shared是:

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM