std::shared_ptr的初始化:make_shared<Foo>()和shared_ptr<T>(new Foo)的区别。

75

什么是以下两者之间的区别:

std::shared_ptr<int> p = std::shared_ptr<int>( new int );

以及

std::shared_ptr<int> p = std::make_shared< int >();

我应该选择哪一个并为什么?

附言:我相当确定这个问题已经被回答过了,但我找不到类似的问题。


2
第二个必须是 std::make_shared<int>() - nosid
5
是的,但在这种情况下不要使用它。始终使用make_shared。有关更多信息,请参阅以下网址:http://herbsutter.com/2013/05/29/gotw-89-solution-smart-pointers/ - nosid
@Deduplicator:我的问题怎么可能是一年后问的那个问题的重复? - Violet Giraffe
@VioletGiraffe:我 认为 另一个稍微好一些,它们涵盖相同的领域。年龄只作为决胜者(尽管在解释文本中不幸地太突出),而旧问题有更高的机会被精心打磨。 - Deduplicator
1
@Deduplicator:好的,虽然我认为我的标题更清晰明了。 - Violet Giraffe
3个回答

101

两个示例都比必要的更冗长:

std::shared_ptr<int> p(new int);  // or '=shared_ptr<int>(new int)' if you insist
auto p = std::make_shared<int>(); // or 'std::shared_ptr<int> p' if you insist

有什么区别?

主要的区别是第一种需要两个内存分配:一个用于受控对象 (new int),另一个用于引用计数。而 make_shared 应该会分配单个内存块,并在其中同时创建两者。

我应该选择哪一个?为什么?

通常情况下你应该使用 make_shared,因为它更有效率。正如其他答案中所指出的,它也避免了任何可能的内存泄露问题,因为你永远不会拥有被管理对象的裸指针。

然而,正如评论中所指出的,它可能存在一个缺点,即如果还有弱引用阻止共享计数被删除,那么内存在对象销毁时就不会被释放。


编辑 2020/03/06:

来自微软官方文档的进一步推荐和相关示例。重点关注示例1片段:

尽可能使用 make_shared 函数在首次创建内存资源时创建 shared_ptr。make_shared 是异常安全的。它使用相同的调用为控制块和资源分配内存,从而减少构造开销。如果不使用 make_shared,则必须使用显式的 new 表达式创建对象,然后将其传递到 shared_ptr 构造函数。以下示例展示了声明和初始化 shared_ptr 以及新对象的各种方法。


23
缺点在于:只要对象存在弱引用,内存块就会一直保留。 - Xeo
2
@Xeo:谢谢,我忘记了那个。 - Mike Seymour
2
@Xeo,你能详细说明一下弱指针问题吗?弱指针不是用来持有内存的,那么在这种情况下为什么会出现这种情况呢? - Trismegistos
2
@Trismegistos:因为共享块(用于保存弱指针和共享指针的数据和计数器)只有在最后一个相关联的“weak_ptr”被销毁后才能释放 - 否则,“weak_ptr”怎么知道它的父对象已经不存在了呢? - Xeo
3
为什么使用 make_shared 会出现这种情况,而使用 std::shared_ptr 构造函数却不会出现这种情况? - Trismegistos
显示剩余2条评论

27

en.cppreference.com上可以看出,使用make_shared声明std::shared_ptr<T> p(Args...)至少进行两次内存分配,这可能会产生不必要的开销。

而且,f(shared_ptr<int>(new int(42)), g())如果g抛出异常可能导致内存泄漏。如果使用make_shared则不存在此问题。因此,如果可能的话我建议使用make_shared方法。


谢谢。很遗憾,在Mac上使用shared_ptrmake_shared存在问题(它们仍然在tr1命名空间中,因此我必须定义宏来编写可移植代码使用共享指针)。我的意思是,需要额外的宏才能使用make_shared - Violet Giraffe
1
很糟糕啊。你用的是哪个编译器?在Debian中,“g++ 4.7”没有这个问题。 - Adri C.S.
1
@VioletGiraffe:在Mac上,您可以指定-stdlib=libc++(在Xcode中找到“C++标准库”在构建设置中选择“libc++”)。这将为您提供一个C++11 std::lib。 - Howard Hinnant
@HowardHinnant:嗯!这是官方的吗?也就是说没有bug了? - Violet Giraffe
@AdriC.S.:clang现在是Mac的官方编译器,而且苹果公司不经常更新它。 - Violet Giraffe
显示剩余2条评论

13

注意,使用make_shared会限制你只能使用默认的内存分配/释放函数,因此如果你想要更多控制权,则不能使用make_shared。换句话说,类似于

std::shared_ptr<uint8_t>(p, [](uint8_t *p){ /*user code */}); 

使用make_shared是不可能的。可以使用allocate_shared,但只能指定分配器而不能指定删除器。有时需要控制封装类的分配和删除。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接