std::make_shared是C++11的一部分,但是std::make_unique很可惜不是。它是在C++14里加入标准库的,但我们可以自己实现make_unique方法。
// 支持普通指针 template<class T,class... Args> inline typename enable_if<!is_array<T>::value,unique_ptr<T>>::type make_unique(Args&&... args){ return unique_ptr<T>(new T(std::forward<Args>(args)...)); } // 支持动态数组 template<class T> inline typename enable_if<is_array<T>::value && extent<T>::value == 0,unique_ptr<T>>::type make_unique(size_t size){ typedef typename remove_extent<T>::type U; return unique_ptr<T>(new U[size]()); } // 过滤掉定长数组的情况 template<class T,class... Args> typename enable_if<extent<T>::value != 0,void>::type make_unique(Args&&...) = delete;enable_if的作用
// Primary template. /// Define a member typedef @c type only if a boolean constant is true. template<bool, typename _Tp = void> struct enable_if { }; // Partial specialization for true. template<typename _Tp> struct enable_if<true, _Tp> { typedef _Tp type; };结合源码可知,当condition==true时,enable_if<condition,T>::type ≡ T,否则报错。
enable_if<!is_array<T>::value,unique_ptr<T>>::type的condition在T不是数组类型时为trueenable_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在这里的作用是实现参数的完美转发,具体见《move和forward源码分析[转]》。
1. 效率更高
shared_ptr需要维护引用计数的信息。如果你通过使用原始的new表达式分配对象,然后传递给shared_ptr(也就是使用shared_ptr的构造函数)的话,shared_ptr的实现没有办法选择,而只能单独的分配控制块:
如果选择使用make_shared的话,情况就会变成下面这样:
内存分配的动作,可以一次性完成。这减少了内存分配的次数,而内存分配是代价很高的操作。
2. 异常安全
看看下面的代码:
void F(const std::shared_ptr<Lhs>& lhs, const std::shared_ptr<Rhs>& rhs) { /* ... */ } F(std::shared_ptr<Lhs>(new Lhs("foo")), std::shared_ptr<Rhs>(new Rhs("bar")));C++是不保证参数求值顺序,以及内部表达式的求值顺序的,所以可能的执行顺序如下:
new Lhs(“foo”))new Rhs(“bar”))std::shared_ptrstd::shared_ptr假设在第2步的时候,抛出了一个异常(比如out of memory,总之,Rhs的构造函数异常了),那么第一步申请的Lhs对象内存泄露了。这个问题的核心在于,shared_ptr没有立即获得裸指针。
我们可以用如下方式来修复这个问题:
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo")); auto rhs = std::shared_ptr<Rhs>(new Rhs("bar")); F(lhs, rhs);当然,推荐的做法是使用std::make_shared来代替:
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));当std::make_shared被调用,指向动态内存对象的原始指针会被安全的保存在返回的std::shared_ptr对象中,然后另一std::make_shared被调用。如果此时产生了异常,那std::shared_ptr析构会知道于是它所拥有的对象会被销毁。
使用std::make_unique来代替new在写异常安全的代码里和使用std::make_shared一样重要。
虽然使用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<A> create() { return std::make_shared<A>(); } protected: A() {} A(const A &) = delete; const A &operator=(const A &) = delete; }; std::shared_ptr<A> foo() { return A::create(); }参考链接
c++11 条款21:尽量使用std::make_unique和std::make_shared而不直接使用new Why Make_shared ? 通过new和make_shared构造shared_ptr的性能差异 How does weak_ptr work? shared_ptr线程安全性分析 How do I call ::std::make_shared on a class with only protected or private constructors?