65.9K
CodeProject 正在变化。 阅读更多。
Home

改进 shared_ptr

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (2投票s)

2012 年 6 月 15 日

CPOL

3分钟阅读

viewsIcon

13513

一种更高效的方式来创建用于 shared_ptr 的对象

为使用 Std::shared_ptr 创建对象

在这篇文章中,我将介绍我最近发现并成功应用于我的项目中的一项优化。它非常容易应用,但能带来非常好的结果——在性能和内存占用方面。

让我们从通常创建 shared_ptr 的方式开始

class Test  
{  
public:  
  Test(int dummy) {}  
}  
...  
  
std::shared_ptr<Test> pTest(new Test(1));

现在让我们逐步看看这里发生了什么

  1. Test 的一个新对象被分配并构造。请注意,它是在堆上分配的,并且每次堆分配都会产生高于 sizeof(Test) 的内存开销。 内存开销在这篇 博文中 进行了更详细的讨论。
  2. 然后将指针传递给 std::shared_ptr<Test> 的构造函数,该构造函数获取新创建对象的控制权。

shared_ptr 的上述构造函数基本上执行两件事:它将其内部指针分配给它将持有的对象,并再次在堆上分配一个共享缓冲区,该缓冲区将用于保存引用计数器。 正如你所看到的,这是另一个内存分配,另一个性能和内存占用打击。因此,更有效的方法是

std::shared_ptr<Test> pTest = std::make_shared<Test>(1);

这样做有几个好处

  • 只有一个内存分配——引用计数器和对象本身都分配在同一个缓冲区中(但是,这取决于实现,因此对于 Visual C++ 10 来说是这种情况)。
  • std::shared_ptr<T> 的构造函数接受一个右值引用作为参数,这使得这种构造可能更有效。

你可能会问,这里有什么缺点?只有几个

  • Visual C++ 10(和 VC11 的 beta 版本)不支持可变模板,因此传递给对象构造函数的参数数量限制为 10 个(在 VC11 中,默认设置为 5 个)。
  • boost 库中的 std::make_shared 不支持此优化,即使在当前最新版本(1.49.0)中,也会分配两个单独的缓冲区——一个用于对象,一个用于引用计数器。

因此,无论如何,如果你在项目中使用了 boost::shared_ptr 并且使用的是从 1.36.0 开始的 boost 版本(make_shared 从 tr1 复制到 boost 库的版本),那么即使你现在无法立即获得好处,迁移到 make_shared 仍然可能是有意义的。

那么简单吗?

不,当我将我的项目迁移到使用 std::make_shared 时,我遇到一个小的陷阱。当对象由某个工厂类创建时,构造函数声明为 private,并且工厂类声明为 friend。问题在于,当工厂使用 make_shared 创建新对象时,构造函数是由一些未知的特定于实现的类调用的,并且由于我们无法将其声明为我们类的 friend,因此调用失败。解决方案不太优雅(至少对我来说),但它有效,并且有助于在应用此优化时真正提高性能。这是一个示例代码

class Obj;  
typedef std::shared_ptr ObjPtr;  
  
class Obj  
{  
  ...  
  private:  
  struct HideMe {}  
  friend class ObjFactory;  
public:  
  Obj(HideMe, int param)  
  { ... }  
}  
  
class ObjFactory  
{   
public:  
  static ObjPtr CreateObj()  
  {  
    return std::make_shared(Obj::HideMe(), param) ;  
  }  
}

因此,实际由 ObjFactory 调用的是 public 构造函数,但由于隐藏结构 Obj::HideMe,没有其他对象可以调用它。

祝优化愉快!

历史

  • 2012 年 6 月 15 日:初始版本
© . All rights reserved.