为什么使用std::make_shared

本文首发于公众号CPP编程
为什么使用std::make_shared_第1张图片

目录

  • 为什么使用std::make_shared
    • 减少重复代码
    • 效率更高
    • 异常安全

为什么使用std::make_shared

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中申请的未被智能指针接管的内存就有泄露的风险。

你可能感兴趣的:(C++进阶,c++)