本文共 3411 字,大约阅读时间需要 11 分钟。
std::make_shared
是C++11的一部分,但是std::make_unique
很可惜不是。它是在C++14里加入标准库的,但我们可以自己实现make_unique
方法。
// 支持普通指针templateinlinetypename enable_if ::value,unique_ptr >::typemake_unique(Args&&... args){ return unique_ptr (new T(std::forward (args)...));}// 支持动态数组template inlinetypename enable_if ::value && extent ::value == 0,unique_ptr >::typemake_unique(size_t size){ typedef typename remove_extent ::type U; return unique_ptr (new U[size]());}// 过滤掉定长数组的情况template typename enable_if ::value != 0,void>::typemake_unique(Args&&...) = delete;
enable_if的作用
// Primary template./// Define a member typedef @c type only if a boolean constant is true.templatestruct enable_if { };// Partial specialization for true.template struct enable_if { typedef _Tp type; };
结合源码可知,当condition==true
时,enable_if<condition,T>::type ≡ T
,否则报错。
enable_if<!is_array<T>::value,unique_ptr<T>>::type
的condition
在T
不是数组类型时为true
enable_if<is_array<T>::value && extent<T>::value == 0,unique_ptr<T>>::type
的condition
在T
为数组类型且数组中元素个数为0时为true
,由于对于非数组类型extent<U>::value
也为0,语句is_array<T>::value
是必要的enable_if<extent<T>::value != 0,void>::type
的condition
在T
类型中元素个数不为0时为true
,即T
为定长数组std::forward的作用
std::forward
在这里的作用是实现参数的完美转发,具体见。
1. 效率更高
shared_ptr
需要维护引用计数的信息。如果你通过使用原始的new
表达式分配对象,然后传递给shared_ptr
(也就是使用shared_ptr
的构造函数)的话,shared_ptr
的实现没有办法选择,而只能单独的分配控制块:
如果选择使用make_shared
的话,情况就会变成下面这样:
内存分配的动作,可以一次性完成。这减少了内存分配的次数,而内存分配是代价很高的操作。
2. 异常安全
看看下面的代码:
void F(const std::shared_ptr& lhs, const std::shared_ptr & rhs) { /* ... */ }F(std::shared_ptr (new Lhs("foo")), std::shared_ptr (new Rhs("bar")));
C++是不保证参数求值顺序,以及内部表达式的求值顺序的,所以可能的执行顺序如下:
new Lhs(“foo”))
new Rhs(“bar”))
std::shared_ptr
std::shared_ptr
假设在第2步的时候,抛出了一个异常(比如out of memory,总之,Rhs
的构造函数异常了),那么第一步申请的Lhs
对象内存泄露了。这个问题的核心在于,shared_ptr
没有立即获得裸指针。
我们可以用如下方式来修复这个问题:
auto lhs = std::shared_ptr(new Lhs("foo"));auto rhs = std::shared_ptr (new Rhs("bar"));F(lhs, rhs);
当然,推荐的做法是使用std::make_shared
来代替:
F(std::make_shared("foo"), std::make_shared ("bar"));
当std::make_shared
被调用,指向动态内存对象的原始指针会被安全的保存在返回的std::shared_ptr
对象中,然后另一std::make_shared
被调用。如果此时产生了异常,那std::shared_ptr
析构会知道于是它所拥有的对象会被销毁。
使用std::make_unique
来代替new
在写异常安全的代码里和使用std::make_shared
一样重要。
make
函数都不允许使用定制删除器,但是std::unique_ptr
和std::shared_ptr
的构造函数都可以。make
函数不能完美传递一个initializer_list
。 替代方案:// initializer_list aa = {1,2,3}; // 或者auto aa = { 1,2,3};auto a = make_shared>(aa);// auto b = make_shared >({1,2,3}); // 错误
虽然使用std::make_shared
可以减少了内存分配的次数,提高效率,但由于控制块与对象都在同一块动态分配的内存上,所以当对象的引用计数变为0,对象被销毁(析构函数被调)后,该对象所占内存仍未释放,直到控制块同样也被销毁,内存才会释放。
我们知道,在控制块中包含两个计数:shared count
和weak count
,分别表示std::shared_ptr
和std::weak_ptr
对对象的引用计数,只有当shared count
和weak count
都为0时,控制块才会被销毁。
换句话说,只要有std::weak_ptr
指向一个控制块(weak count
大于0),那控制块就一定存在。只要控制块存在,包含它的内存必定存在。通过std::shared_ptr
的make
函数分配的内存在最后一个std::shared_ptr
和最后一个std::weak_ptr
被销毁前不能被释放。
make_shared
。 替代方案:class A {public: static std::shared_ptr create() { return std::make_shared(); }protected: A() {} A(const A &) = delete; const A &operator=(const A &) = delete;};std::shared_ptr foo() { return A::create();}
参考链接