Kagamine Len
文章20
标签10
分类2
shareptr

shareptr

image-20210429220617927

为什么使用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就是存放资源对象的内存块。

placement new 会在已经开辟的空间中存放这个sotrage对象,即把对象放在指定的空间位置
那_Storage是怎么来的?它其实是一个联合体,编译器在编译时能获取到资源对象的大小,然后利用模板实例化出具有相等大小的联合体或结构体,这个联合体或结构体的对象就可以用于存放资源对象。

image-20210429222958679

unique_ptr

image-20210429223330843

此处,unique_ptr采用了移动拷贝构造,虽然其普通拷贝构造是允许的,但是右值拷贝构造是允许的,因为编译器知道当前拷贝的这个对象将会被销毁,因此其仍然会调用该拷贝函数

左值和右值

image-20210429223525254

如 函数返回的值为右值,nullptr为字面量为右值

函数返回引用时返回的为左值

weak_ptr

_Atomic_word  _M_use_count;     // #shared
_Atomic_word  _M_weak_count;    // #weak + (#shared != 0)

由上述代码可见,实际上share_ptr和weak_ptr上都有指向一个计数类,包括对share_ptr的计数和对weak_ptr+(#shared!=0)的计数

即对于weak的计数来说,就算没有任何weak_ptr,其初始化值仍为1

那么weak_ptr什么时候判断share==0了呢?

if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
	  {
            _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
	    _M_dispose();
	    // There must be a memory barrier between dispose() and destroy()
	    // to ensure that the effects of dispose() are observed in the
	    // thread that runs destroy().
	    // See http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html
	    if (_Mutex_base<_Lp>::_S_need_barriers)
	      {
		__atomic_thread_fence (__ATOMIC_ACQ_REL);
	      }

            // Be race-detector-friendly.  For more info see bits/c++config.
            _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
	    if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count,
						       -1) == 1)//此处 当share_ptr被释放时,会检测有无weak_ptr对象仍然引用了该计数,如果没有了,才会释放该计数器
              {
                _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
	        _M_destroy();
              }
	  }
      }
×