6

我遇到了这篇文章和@kerek SB 的答案之一

std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

在您的代码中,第二个变量只是一个裸指针,根本不是共享指针。

现在在肉上。make_shared (实际上)更有效,因为它在一个动态分配中将引用控制块与实际对象一起分配。相比之下,接受裸对象指针的 shared_ptr 的构造函数必须为引用计数分配另一个动态变量。权衡是 make_shared(或其表亲 allocate_shared)不允许您指定自定义删除器,因为分配是由分配器执行的。

(这不影响对象本身的构造。从Object的角度来看,两个版本之间没有区别。更有效的是共享指针本身,而不是托管对象。)

现在我对这篇文章有两个问题,如果有人能澄清这一点,我将不胜感激

  1. 为什么第二个不是共享指针?这不会增加引用计数吗

  2. make_shared 如何只分配一个内存而 new 分配两个从而使 make_shared 更有效?

对此稍作澄清将不胜感激。

4

4 回答 4

9

在那个问题中,“第二个变量”指的是这一行:

auto ptr_res2(new Object("new")); // this creates an Object*

不是这个:

std::shared_ptr<Object> p2(new Object("foo")); // this creates a shared_ptr<Object>

为什么使用一种分配更有效的最佳解释make_shared是比较图像。这是std_shared_ptr<Object>(new Object)看起来的样子:

在此处输入图像描述

shared_ptr一个Widget*,但它与 ref 计数器不在同一个内存块中,因为它们是单独分配的。被Widget*传入,并且引用计数块是在内部分配的,这就是为什么Widget在单独的内存空间中。

另一方面,这是一个分配的样子:

在此处输入图像描述

(我从 Herb Sutter 那里偷了两张照片)。我们仍然需要一个Widget和一个引用计数块,但是整个内存块在一次调用new/时被抓取malloc,大小足够,所以Widget和引用计数块最终在内存中是连续的。

于 2014-11-22T21:48:08.453 回答
3
  1. 被称为第二个变量的代码实际上是这样的(取自 OP 的代码):

    auto ptr_res2(new Object("new"));
    

    这不会创建一个std::shared_ptr,而是创建一个指向 的指针Object

  2. std::shared_ptr当使用它的构造函数创建一个使用裸指针的构造函数时,您必须将一个指针传递给已分配的内存(例如使用分配的内存new)。std::shared_ptr这意味着在创建对象本身时已经为对象分配了内存。std::shared_ptr需要为其自己的工作分配内存,例如引用计数器。所以有两种分配:一种是使用new传递给 ctor 的std::shared_ptr,另一种是在构造std::shared_ptr自身时需要的。

    Object* ptr = new Object{"Foo"}; // Allocation 1 (object).
    std::shared_ptr<Object> p1{ptr}; // Allocation 2 (internal counters).
    

    辅助函数std::make_shared仅使用 1 次分配,因为您将构造对象所需的参数传递给它,而不是指向对象本身的指针。然后可以在同时保存对象和引用计数器的情况下分配内存。std::make_shared

    auto p2 = std::make_shared<Object>{"Foo"} // Allocation 1 (object & counter).
    
于 2014-11-22T21:56:27.707 回答
2

为什么第二个不是共享指针?这不会增加引用计数吗

我相信引用是指原始海报的代码,它声称创建了一个智能指针,但实际上并没有这样做。ptr_res2只是一个普通的指针。

cout << "Create smart_ptr using new..." << endl;
auto ptr_res2(new Object("new"));
cout << "Create smart_ptr using new: done." << endl;

make_shared 如何只分配一个内存而 new 分配两个从而使 make_shared 更有效

make_shared需要为计数器和对象本身分配一个槽。可以一次性分配内存,然后将其中的一部分用于计数器,将其余部分用于对象。

请注意,这也意味着计数器和对象本身在内存中彼此相邻,从而提高了数据局部性。

于 2014-11-22T21:46:16.457 回答
1
  1. 第二个仍然是共享指针。它正在调用 shared_ptr 的构造函数:

http://www.cplusplus.com/reference/memory/shared_ptr/shared_ptr/

  1. 但是,使用 make_shared 更有效,因为它只进行一次分配而不是 2 次分配。请记住,shared_ptr 需要堆上的空间供 Object 使用,并且管理器还需要跟踪共享指针和弱指针的数量。如果您使用构造函数,则为 Object 分配空间,然后将指针传递给需要为管理器分配空间的构造函数。相反,如果您使用 make_shared,它会分配一块内存来存储 Object 和管理器。因为分配相对昂贵,所以一次分配比两次分配要好。
于 2014-11-22T21:46:49.817 回答