简体   繁体   English

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

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

Thanks to std::make_shared , I wonder, whether the constructor for std::shared_ptr , which takes a raw pointer has any value except when interfacing with legacy / library code, eg when storing the output of a factory. 感谢std::make_shared ,我想知道,带有原始指针的std::shared_ptr的构造函数是否具有除了与传统/库代码接口时的任何值,例如存储工厂的输出时。

  • Are there other legitimate use-cases? 还有其他合法用例吗?
  • Is it reasonable advice to avoid that constructor? 避免使用构造函数是否合理?
  • Even to the extend of putting code checks for it in place that warn the programmer whenever it is used? 甚至对代码进行检查的范围是什么,它会在程序员使用它时发出警告?
  • Should the same guidelines (whatever they are) apply to shared_ptr<T>::reset(T*) ? 相同的指南(无论它们是什么)是否适用于shared_ptr<T>::reset(T*)

Regarding the code checks: I know that interfacing with legacy / library code is quite common, so automated code checks might be problematic, but in most cases I encountered so far, I'd rather use a unique_ptr anyway and I'm also not talking about a compiler warning that pops up at -Wall , but rather about a rule for static code analysis during code review. 关于代码检查:我知道与遗留/库代码的接口很常见,因此自动代码检查可能会有问题,但在目前为止我遇到的大多数情况下,我宁愿使用unique_ptr而且我也不会说话关于在-Wall弹出的编译器警告,而是关于代码审查期间静态代码分析的规则。


My motivation: 我的动机:
It is relatively easy to say "Don't use std::shared_ptr<T>(new T(...)) , always prefer std::make_shared<T>(...) " (Which I believe is correct advise?). 可以相对容易地说“不要使用std::shared_ptr<T>(new T(...)) ,总是更喜欢std::make_shared<T>(...) (我相信这是正确的建议?)。 But I'm wondering if it isn't a design smell in general, if one has to create a shared_ptr from a raw pointer, even - or especially - if the object was not just created via new , because the object should have been created either as a "shared" or "unique" object in the first place. 但是我想知道它是不是一般的设计气味,如果必须从原始指针创建一个shared_ptr ,甚至 - 或者特别是 - 如果该对象不是刚刚通过new创建的,因为该对象应该已经创建或者首先作为“共享”或“独特”对象。

First use case that pops to mind is when the deleter is not the default delete . 弹出的第一个用例是当删除器不是默认delete

Eg in a windows environment COM objects must some times be used, the release of these objects must be done on the object itself, via Release . 例如,在Windows环境中,必须有时使用COM对象,必须通过Release在对象本身上完成这些对象的Release Sure ATL can be used, but not everyone wants to use it. 当然可以使用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());

A more uncommon situation - but still valid - is when an object (handle or even raw memory) is custom allocated in a dll, OS or library and has it's own associated cleanup function that must be called (which may or may not call delete in turn). 更常见的情况 - 但仍然有效 - 是在dll,OS或库中自定义分配对象(句柄或甚至原始内存)并且具有必须调用的自己的关联清理函数(可能会也可能不会调用delete )转)。 If memory allocation is involved std::allocate_shared offers more enhanced control of the allocator that is to be used without exposing a raw pointer. 如果涉及内存分配, std::allocate_shared提供了对使用的分配器的更多增强控制,而不暴露原始指针。

My personal feeling is that given std::make_shared and std::allocate_shared , the requirement to construct a shared_ptr from raw pointers is becoming less and less. 我个人的感觉是,给定std::make_sharedstd::allocate_shared ,从原始指针构造shared_ptr的要求越来越少。 Even the cases above, can be wrapped into utility allocation and management functions, removing from the main business logic code. 即使是上面的情况,也可以包含在公用事业分配和管理功能中,从主业务逻辑代码中删除。

Scott Meyers lists a couple of exceptions in Effective Modern C++ Scott Meyers列出了Effective Modern C ++中的几个例外

The first has already been mentioned. 第一个已被提及。 If you need to supply a custom deleter you can't use make_shared . 如果您需要提供自定义删除器,则无法使用make_shared

The second is if you are on a system with memory concerns and you are allocating a very large object then using make_shared allocates one block for both the object and the control block that includes the weak reference count. 第二个是如果你在一个有内存问题的系统上并且你正在分配一个非常大的对象,那么使用make_shared为对象和包含引用计数的控制块分配一个块。 If you have any weak_ptr s pointing to the object then the memory cannot be deallocated. 如果有任何weak_ptr指向该对象,则无法释放内存。 On the other hand if you had not used make_shared then as soon as the last shared_ptr is deleted then the memory for the very large object can be deallocated. 另一方面,如果你没有使用make_shared那么一旦删除了最后一个shared_ptr就可以释放非常大的对象的内存。

• Are there other legitimate use-cases? •是否有其他合法用例?

Yes: there is a situation when a resource doesn't map to new/delete: 是:资源未映射到新/删除的情况:

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

In this case, you will want to use the constructor directly. 在这种情况下,您将需要直接使用构造函数。

• Is it reasonable advice to avoid that constructor? •避免使用构造函数是否合理?

When the functionality overlaps with make_shared, yes. 当功能与make_shared重叠时,是的。

• Should the same guidelines (whatever they are) apply to shared_ptr::reset(T*)? •相同的指南(无论它们是什么)是否适用于shared_ptr :: reset(T *)?

I think they shouldn't. 我认为他们不应该。 If you really want, you can replace the reset call with an assignment with the result of a std::make_shared call. 如果您真的想要,可以使用带有std :: make_shared调用结果的赋值替换重置调用。 I would prefer to see the reset call instead though - it would be more explicit in intent. 我更愿意看到重置调用 - 它在意图中会更明确。

Regarding the code checks: I know that interfacing with legacy / library code is quite common 关于代码检查:我知道与遗留/库代码的接口很常见

Consider wrapping the third party library into an interface that ensures the returned values are unique_ptr-wrapped. 考虑将第三方库包装到一个接口中,以确保返回的值是unique_ptr-wrapped。 This will provide you with a point of centralization and the opportunity for other convenience/safety optimizations. 这将为您提供集中点和其他便利/安全优化的机会。

It is relatively easy to say [...] (Which I believe is correct advise?). 相对容易说[...](我相信这是正确的建议?)。 But I'm wondering if it isn't a design smell in general 但我想知道它是不是一般的设计气味

It is not design smell to use it; 使用它不是设计气味; only to use it when std::make_shared/std::make_unique would work just as well . 只有当std :: make_shared / std :: make_unique 也能正常工作时才使用它。

Edit : to address the point of the question: you will probably not be able to add a static-analysis rule for this, unless you also add a convention/exception list to it (ie "except for custom deleters and third party lib API adaptation layers, make_shared and make_unique should always be used"). 编辑 :解决问题的要点:您可能无法为此添加静态分析规则,除非您还向其添加约定/例外列表(即“自定义删除和第三方lib API适配除外”应始终使用layers,make_shared和make_unique“)。 Such a rule would probably be skipped for some of your files. 某些文件可能会跳过这样的规则。

When you do use the constructors directly though, it may be a good idea to have them in a function dedicated to just that (similar to what make_unique and make_shared do): 当您直接使用构造函数时,将它们放在专用于此的函数中可能是个好主意(类似于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
}

Use std::make_shared whenever you can. 尽可能使用std::make_shared

There are two reasons you may not get away with it though: 有两个原因你可能无法逃脱:

  • You want to use a custom deleter for your object. 您想为对象使用自定义删除器。 This might be the case if you interface with code that does not use RAII. 如果您使用不使用RAII的代码进行交互,则可能就是这种情况。 Other answers provide more details. 其他答案提供了更多细节。

  • You've got a custom new operator that you want to call. 您有一个要调用的自定义新运算符。 std::make_shared can't call it for you, because the whole point of it is to allocate memory once (though not required by the standard). std::make_shared不能为你调用它,因为它的重点是分配内存一次 (虽然标准不需要)。 See http://ideone.com/HjmFl1 . http://ideone.com/HjmFl1

The reasoning behind std::make_shared is not that you avoid the new keyword, but that you avoid a memory allocation. std::make_shared背后的std::make_shared并不是你避免使用new关键字,而是避免了内存分配。 A shared pointer needs to set up some control data structure. 共享指针需要设置一些控制数据结构。 So when setting up a shared_ptr without make_shared , you have to do two allocations: One for the object, another one for the control structure. 因此,在没有make_shared情况下设置shared_ptr ,必须进行两次分配:一次用于对象,另一次用于控制结构。 make_shared (usually) only allocates one (larger) block of memory and constructs the contained object and the control structure in place. make_shared (通常)只分配一个(更大的)内存块,并构造包含的对象和控制结构。 The reason behind make_shared is (initially was) that it is more efficient in most cases, not that the syntax is prettier. make_shared背后的make_shared是(最初是)在大多数情况下它更有效,而不是语法更漂亮。 This is also the reason why there was no std::make_unique in C++11. 这也是为什么C ++ 11中没有std::make_unique的原因。 (As ChrisDrew pointed out, I suggested that std::make_unique was only added for symmetry/prettier syntax. It can help to write exception safe code in a more compact way though, see Herb Sutter's GotW #102 or this question .) (正如ChrisDrew所指出的那样,我建议std::make_unique只是为了对称/更漂亮的语法而添加。它可以帮助以更紧凑的方式编写异常安全代码,参见Herb Sutter的GotW#102这个问题 。)

In addition to the other answers, you cannot use make_shared if the constructor is private, such as from a factory function. 除了其他答案之外,如果构造函数是私有的,则不能使用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();
};

Many of the answers provide at least one unique aspect, so I decided to make a summary answer. 许多答案提供了至少一个独特的方面,所以我决定做一个总结答案。 Credit goes to @Niall, @Chris Drew, @utnapistim, @isanae, @Markus Mayr and (by proxy) Scott Meyers. 归功于@Niall,@ Chris Drew,@ utnapistim,@ isanae,@ Markus Mayr和(代理人)Scott Meyers。

Reasons, why you might not want / might not be able to use make_shared are: 您可能不希望/可能无法使用make_shared是:

  • You have to use a custom deleter and/or even a custom allocator/new operator. 您必须使用自定义删除器和/或甚至自定义分配器/新运算符。
    This is probably the most common reason, especially when interfacing with 3rd party libraries. 这可能是最常见的原因,尤其是在与第三方库连接时。 std::allocate_shared might help in some of those situations. std::allocate_shared可能在某些情况下有所帮助。
  • If memory is a concern and you often have weak pointers that outlive the object. 如果内存是一个问题,你经常有弱指针,它比对象寿命更长。
    As the managed object is created on the same chunk of memory as the control block, the memory cannot be freed until also the last weak pointer gets destroyed. 由于托管对象是在与控制块相同的内存块上创建的,因此在最后一个弱指针被破坏之前,不能释放内存。
  • If you have a private constructor and eg only a public factory function. 如果您有私有构造函数,例如只有公共工厂函数。
    Making make_shared a friend is not a viable solution in that case, as the actual construction might happen in some helper function/class. 在这种情况下,使make_shared成为朋友并不是一个可行的解决方案,因为实际的构造可能发生在某个辅助函数/类中。

Regarding the code checks, the exceptions listed above are probably too many for an automatic check of a "Use make_shared whenever possible" guideline, as well as for calling a violation of that guideline a design smell in its own right. 关于代码检查,上面列出的例外可能太多,无法自动检查“尽可能使用make_shared指南,以及将违反该指南的行为本身称为设计气味。

As a bright spot on the horizon, even if a library requires custom allocator and deallocator functions and we can't / don't want to use std::allocate_shared , we can make our own version of make shared that at least encapsulates the allocation and deletion call and increases exception safety (although it most probably doesn't offer the single allocation advantage). 作为一个亮点,即使一个库需要自定义分配器和解除分配器功能,我们也不能/不想使用std::allocate_shared ,我们可以制作我们自己的make共享版本至少封装分配和删除调用并增加异常安全性(尽管它很可能不提供单一的分配优势)。

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

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