make_shared真的比new更有效率吗
Is make_shared really more efficient than new?
我用C++11中的shared_ptr
和make_shared
进行了实验,并编写了一个小玩具示例来查看调用make_shared
时实际发生了什么。作为基础设施,我在XCode4中使用了llvm/clang 3.0以及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:done创建smart_ptr。
使用新创建smart_ptr。。。
新建
使用new:done创建smart_ptr。
自毁
自毁
make_shared
似乎调用了复制构造函数两次。如果我使用常规new
为Object
分配内存,则不会发生这种情况,只构造一个Object
。
我想知道的是以下内容。我听说make_shared
应该比使用new
(1,2)更有效。一个原因是make_shared
将引用计数与要管理的对象一起分配在同一内存块中。好吧,我明白了。这当然比两个单独的分配操作更有效率。
相反,我不明白为什么这需要两次调用Object
的复制构造函数。因此,我不相信在每个的情况下,make_shared
比使用new
的分配更有效。我错了吗?好吧,可以为Object
实现一个移动构造函数,但我仍然不确定这是否比仅仅分配Object
到new
更有效。至少不是在所有情况下。如果复制CCD_ 17比为参考计数器分配内存便宜,那将是正确的。但是shared_ptr
内部引用计数器可以使用两个基元数据类型来实现,对吧?
您能否帮助并解释为什么make_shared
在效率方面是可行的,尽管有概述的复制开销?
作为基础设施,我在XCode4中使用了llvm/clang 3.0以及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
将其放置到位。
所以你得到的结果与标准不一致。LLVM的libc++在这一点上被打破了。提交错误报告。
作为参考,以下是我将您的代码带入VC2010:时发生的事情
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
我还将它移植到Boost的原始shared_ptr
和make_shared
,得到了与VC2010相同的东西。
我建议提交一份错误报告,因为libc++的行为已被破坏。
您必须比较这两个版本:
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
的角度来看,这两个版本没有区别。更有效的是共享指针本身,而不是托管对象。)
因此,需要记住的一件事是您的优化设置。在没有启用优化的情况下,衡量性能,特别是与c++相关的性能是没有意义的。我不知道您是否真的使用优化进行了编译,所以我认为值得一提。
也就是说,你用这个测试测量的是而不是make_shared
更有效的一种方式。简单地说,你测量的东西不对:-P。
这是交易。通常,创建共享指针时,它至少有2个数据成员(可能更多)。一个用于指针,一个用于引用计数。这个引用计数是在堆上分配的(这样它就可以在不同寿命的shared_ptr
之间共享……这毕竟是重点!)
因此,如果您正在创建一个具有类似std::shared_ptr<Object> p2(new Object("foo"));
的对象,则至少有2个对new
的调用。一个用于Object
,一个用于引用计数对象。
make_shared
可以选择(我不确定它必须这样做),做一个足够大的new
,将指向的对象和引用计数保存在同一个连续块中。有效地分配一个看起来像这样的对象(说明性的,而不是字面上的)。
struct T {
int reference_count;
Object object;
};
由于引用计数和对象的寿命是绑定在一起的(其中一个比另一个寿命长是没有意义的)。整个块也可以同时是CCD_ 41d。
因此,效率在于分配,而不是复制(我怀疑这与优化有关)。
需要明确的是,这就是boost对make_shared
的看法
http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/make_shared.html
除了方便和风格,这样的功能也是例外安全的而且速度要快得多,因为它可以对对象及其相应的控制块,消除sharedptr的构建开销的很大一部分。这消除了关于sharedptr的主要效率抱怨之一。
您不应该在那里获得任何额外的副本。输出应为:
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"太多了,所以你用来获得输出的代码必须与你发布的代码不同)
make_shared
更高效,因为它可以只使用一个动态分配而不是两个动态分配来实现,并且因为它需要为每个共享对象保留一个指针的内存更少的账簿。
编辑:我没有检查Xcode 4.2,但使用Xcode 4.3,我得到了上面显示的正确输出,而不是问题中显示的错误输出。
- 如果"new int"返回"int*",那么为什么"new int[n]"不返回"int**"?
- 有没有一种方法可以使用placement new将堆叠对象分配给分配的内存
- 重载运算符new[]的行为取决于析构函数
- 过载'operator new'如何导致无限循环?
- 创建具有 new in 函数和"this is nullptr"异常的对象
- Codelite C++ new project
- 在类c++中使用new声明数组
- g++用户定义的动态链接库上的全局new和delete运算符
- 如果整个应用程序是虚拟映射的,为什么 new 会进行系统调用?
- 在将 new 与指针一起使用时,创建数组的指定长度
- 体系结构x86_64的未定义符号:std:terminate(),typeinfo,运算符delete[],运算符new
- 为什么我的全局 new() 覆盖被绕过了?
- 声明C++数组(带或不带 "new" 关键字)
- 在C++中,如果我可以直接将整数分配给指针而不使用"new",为什么要使用"new"?
- 是否可以使用 new 指定具有宏常量的动态分配数组的元素?
- constexpr new 如何分配内存?
- 放置 new 和 uninitialized_fill() 的行为
- 使用 new 和 值进行数组初始化,但没有显式数量的元素
- different between int **arr =new int [ n]; and int a[i][j]?
- make_shared真的比new更有效率吗