我正在尝试使用C ++ 11中的shared_ptr
和make_shared
并编写一个小玩具示例,以便在调用make_shared
时查看实际发生的情况。作为基础设施,我使用llvm / clang 3.0以及XCode4中的llvm std c ++库。
class Object
{
public:
Object(const string& str)
{
cout << "Constructor " << str << endl;
}
Object()
{
cout << "Default constructor" << endl;
}
~Object()
{
cout << "Destructor" << endl;
}
Object(const Object& rhs)
{
cout << "Copy constructor..." << endl;
}
};
void make_shared_example()
{
cout << "Create smart_ptr using make_shared..." << endl;
auto ptr_res1 = make_shared<Object>("make_shared");
cout << "Create smart_ptr using make_shared: done." << endl;
cout << "Create smart_ptr using new..." << endl;
shared_ptr<Object> ptr_res2(new Object("new"));
cout << "Create smart_ptr using new: done." << endl;
}
现在看一下输出,请:
使用make_shared创建smart_ptr ...
构造函数make_shared
复制构造函数......
复制构造函数......
析构
析构
使用make_shared创建smart_ptr:done。
使用new ...
创建smart_ptr构造函数new
使用new:done。
创建smart_ptr析构
析构
make_shared
似乎正在调用复制构造函数两次。如果我使用常规Object
为new
分配内存,则不会发生这种情况,只会构建一个Object
。
我想知道的是以下内容。我听说make_shared
应该比使用new
(1,2)更有效率。一个原因是因为make_shared
将引用计数与要在同一内存块中管理的对象一起分配。好的,我明白了。这当然比两个单独的分配操作更有效。
相反,我不明白为什么这需要两次调用Object
的复制构造函数。因此,我不相信make_shared
比使用每个案例中的new
分配效率更高。我错了吗?好吧,有人可以为Object
实现移动构造函数,但我仍然不确定这是否比仅通过Object
分配new
更有效。至少在每种情况下都不是。如果复制Object
比为参考计数器分配内存便宜,那将是真的。但是shared_ptr
- 内部参考计数器可以使用几种原始数据类型来实现,对吧?
你能帮助并解释为什么make_shared
是效率方面的方法,尽管概述了副本开销?
答案 0 :(得分:37)
作为基础设施,我使用llvm / clang 3.0以及XCode4中的llvm std c ++库。
那似乎是你的问题。 C ++ 11标准在第20.7.2.2.6节中说明了make_shared<T>
(和allocate_shared<T>
)的以下要求:
需要:表达式:: new(pv)T(std :: forward(args)...),其中pv的类型为void *并且指向适合存放类型T的对象的存储,应该很好地形成。 A应为分配器(17.6.3.5)。 A的拷贝构造函数和析构函数不会抛出异常。
T
不必须是可复制构造的。实际上,T
甚至不需要是非展示新的可构造的。它只需要就地构建。这意味着make_shared<T>
T
唯一可以new
就位Create smart_ptr using make_shared...
Constructor make_shared
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor
Destructor
就位。
所以你得到的结果与标准不一致。在这方面,LLVM的libc ++被打破了。提交错误报告。
作为参考,这是我将代码带入VC2010时发生的事情:
shared_ptr
我还将它移植到Boost的原始make_shared
和{{1}},我和VC2010一样。
我建议提交错误报告,因为libc ++的行为已被破坏。
答案 1 :(得分:32)
您必须比较这两个版本:
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
的角度来看,两个版本之间没有区别。更有效的是共享指针本身,而不是托管对象。)
答案 2 :(得分:6)
因此要记住的一件事是优化设置。测量性能,尤其是关于c ++的性能无意义,未启用优化。我不知道你是否真的用优化编译,所以我认为值得一提。
那就是说,你用这个测试测量的是不一种make_shared
更有效的方式。简单地说,你正在测量错误的东西:-P。
这是交易。通常,当您创建共享指针时,它至少有2个数据成员(可能更多)。一个用于指针,一个用于引用计数。这个引用计数是在堆上分配的(这样它就可以在shared_ptr
之间以不同的生命周期共享......毕竟这就是重点!)
因此,如果您要使用std::shared_ptr<Object> p2(new Object("foo"));
之类的内容创建对象,则new
至少会有 2 次调用。一个用于Object
,一个用于引用计数对象。
make_shared
有选项(我不确定必须),做一个new
,它足以容纳指向的对象,引用计数在同一个连续的块中。有效地分配一个看起来像这样的对象(说明性的,而不是字面意思)。
struct T {
int reference_count;
Object object;
};
由于引用计数和对象的生命周期是捆绑在一起的(一个人比另一个人活得更久没有意义)。整个块也可以同时delete
d。
因此,效率是在分配中,而不是在复制中(我怀疑与优化相比更多)。
要明确的是,这就是关于make_shared
http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/make_shared.html
除了方便和风格,这样的功能也是例外安全的 并且相当快,因为它可以使用单个分配 对象及其对应的控制块,消除了一个 shared_ptr的构造开销的重要部分。这个 消除了关于shared_ptr的一个主要效率投诉。
答案 3 :(得分:3)
你不应该在那里获得任何额外的副本。输出应为:
Create smart_ptr using make_shared...
Constructor make_shared
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor
我不知道为什么你会得到额外的副本。 (虽然我看到你得到一个'析构函数'太多了,所以用于获取输出的代码必须与你发布的代码不同)
make_shared
更有效率,因为它只能使用一个动态分配而不是两个来实现,因为它需要一个指针的内存值,而不是每个共享对象的簿记。
编辑:我没有用Xcode 4.2检查,但是使用Xcode 4.3我得到了上面显示的正确输出,而不是问题中显示的错误输出。