std::shared_ptr是常用的智能指针,建立一个shared_ptr对象有两种方式:
// (1)
std::shared_ptr<Widget> p1(new Widget);
// (2)
std::shared_ptr<Widget> p2(std::make_shared<Widget>());
通常方法(2)使用make_shared是更受推荐的做法。原因是
对于
// (2)
std::shared_ptr<Widget> p2(std::make_shared<Widget>());
它可以简化为
// (2)
auto p2(std::make_shared<Widget>());
对比(1)少书写了一次Widget,在代码中减少重复总是一件好事,对吧:)
对于
// (1)
std::shared_ptr<Widget> p1(new Widget);
存在两次内存分配操作:
1.new Widget
2.为p1分配控制块(control block),控制块用于存放引用计数等信息
而对于
// (2)
auto p2(std::make_shared<Widget>());
只有一次内存内配操作,make_shared会一次性申请足够大的空间用于存放Widget对象和智能指针的控制块。
在MSVC版本的STL中,make_shared的实现是
template<class _Ty,
class... _Types> inline
shared_ptr<_Ty> make_shared(_Types&&... _Args)
{
// make a shared_ptr
_Ref_count_obj<_Ty> *_Rx =
new _Ref_count_obj<_Ty>(_STD forward<_Types>(_Args)...);
shared_ptr<_Ty> _Ret;
_Ret._Resetp0(_Rx->_Getptr(), _Rx);
return (_Ret);
}
这里的_Ref_count_obj类包含成员变量:
1.控制块
2.一个内存块,用于存放智能指针管理的资源对象
所以new _Ref_count_obj能一次性为控制块和资源对象申请内存。
感兴趣的读者可以再看看_Ref_count_obj的构造函数:
template<class... _Types>
_Ref_count_obj(_Types&&... _Args)
: _Ref_count_base()
{
// construct from argument list
::new ((void *)&_Storage) _Ty(_STD forward<_Types>(_Args)...);
}
此处其实也有一个new操作,但是是placement new,不涉及内存分配。所以内存分配操作还是只有一次。
引用计数在_Ref_count_obj的父类_Ref_count_base中。而_Storage就是存放资源对象的内存块。
那_Storage是怎么来的?它其实是一个联合体,编译器在编译时能获取到资源对象的大小,然后利用模板实例化出具有相等大小的联合体或结构体,这个联合体或结构体的对象就可以用于存放资源对象。
假设有如下代码:
foo(shared_ptr<Widget>(new Widget), bar());
在调用foo之前,有三个subexpressions需要处理:
1.bar()
2.new Widget
3.shared_ptr
编译器保证2在3之前执行,却不保证1不会在2和3之间发生。所以如果执行顺序是2-1-3,且bar()抛出了异常,那在2中申请的未被智能指针接管的内存就有泄露的风险。