改进 shared_ptr






4.50/5 (2投票s)
一种更高效的方式来创建用于 shared_ptr 的对象
为使用 Std::shared_ptr 创建对象
在这篇文章中,我将介绍我最近发现并成功应用于我的项目中的一项优化。它非常容易应用,但能带来非常好的结果——在性能和内存占用方面。
让我们从通常创建 shared_ptr
的方式开始
class Test
{
public:
Test(int dummy) {}
}
...
std::shared_ptr<Test> pTest(new Test(1));
现在让我们逐步看看这里发生了什么
- 类
Test
的一个新对象被分配并构造。请注意,它是在堆上分配的,并且每次堆分配都会产生高于sizeof(Test)
的内存开销。 内存开销在这篇 博文中 进行了更详细的讨论。 - 然后将指针传递给
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 日:初始版本