一 make_shared
1.1 make_shared 是什么?
c++ 11 中 引入了智能指针 shared_ptr,以及一个模板函数 make_shared 来生成一个制定类型的 shared_ptr。
1.2 引入 make_shared ,解决了什么问题?
make_shared的引入,主要有两点的提升:性能 与 异常安全
C++11 make_shared - 简书 (jianshu.com)
- 性能
有如下两种方式生成 shared_ptr
int main()
{// 1.shared_ptr<string> p1(new string("66888"));cout << *p1 << endl;// 2.shared_ptr<string> p2 = make_shared<string>("888888");cout << *p2 << endl;return 0;
}
使用 make_shared 性能要更好一些
shared_ptr<string> p1(new string("66888")); 会分配两次内存:
每个std::shared_ptr都指向一个控制块,控制块包含被指向对象的引用计数以及其他东西。这个控制块的内存是在std::shared_ptr的构造函数中分配的。因此直接使用new,需要一块内存分配给string,还要一块内存分配给控制块。
使用 shared_ptr<string> p2 = make_shared<string>("888888"); 分配一次内存,
一次分配就足够了。这是因为std::make_shared申请一个单独的内存块来同时存放 string 对象和控制块。
- 异常安全
// d.h
#include<iostream>class D
{
public:D(){std::cout << "constructor D " << std::endl;}~D(){std::cout << "destructor D " << std::endl;}};// main.cppint getVal()
{throw 888;return 66;
}void init(shared_ptr<D> ptr, int val)
{}int main()
{// 1. init(std::shared_ptr<D>(new D), getVal());// 2.init(std::make_shared<D>(), getVal());return 0;
}
第 1 种的调用方式分为三步:
1. new D
2. 调用 shared_ptr 类的构造函数
3. 调用 getVal 函数
针对不同的编译器,上述三步的执行顺序可能不同,若是 其中的第 2 步 与第 3 步发生了调换,那么在执行 getVal 抛出异常后,就不会再执行 第 2 步,没有调用 shared_ptr 的构造函数,那么就无法用 shared_ptr 来管理 第 1 步分配出的内存。
第 2 种方式,用 make_shared 就不会出现该问题,其分为两步
1. make_shared 生成 shared_ptr 指针
2.调用 getVal 函数
上面的 1 步 与 2 步,即便发生顺序调换,也不会出现内存无法管理的情况
1.3 make_shared 源码解析
template<typename _Tp, typename... _Args>inline shared_ptr<_Tp>make_shared(_Args&&... __args){typedef typename std::remove_const<_Tp>::type _Tp_nc; // 去除 _Tp 的 const 特性,获取到其类本身return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(),std::forward<_Args>(__args)...);// std::allocator会给我们分配一块_Tp_nc实例需要的内存空间// 完美转发(perfect forward)剩余构造函数的参数}template<typename _Tp, typename _Alloc, typename... _Args>inline shared_ptr<_Tp>allocate_shared(const _Alloc& __a, _Args&&... __args){return shared_ptr<_Tp>(_Sp_make_shared_tag(), __a,std::forward<_Args>(__args)...); // 调用 shared_ptr 的 private 构造函数}
make_shared 的参数是万能引用 && ,因此参数既可以接受左值也可以接受右值。
allocate_shared 是 shared_ptr 类的 friend 函数,因此可以调用 shared_ptr 的 private 构造函数。
总之,下面的 private 构造函数将实例内存与计数器模块的内存绑定在了一起。
template<typename _Alloc, typename... _Args>__shared_ptr(_Sp_alloc_shared_tag<_Alloc> __tag, _Args&&... __args)
详细解析见:从零开始写一个shared_ptr-make_shared源代码解析 - 知乎 (zhihu.com)
1.4 make_shared 使用例子
// person.h
class Person
{
public:Person(std::string name);Person(const Person& p);~Person();std::string& getName();void setName(std::string& name);private:std::string m_name;
};// person.cpp
#include "person.h"
#include<iostream>
Person::Person(std::string name):m_name(name)
{std::cout << "Person constructor name: " << m_name << std::endl;
}Person::Person(const Person& p)
{this->m_name = p.m_name;std::cout << "Person copy constructor name: " << this->m_name << std::endl;
}Person::~Person()
{std::cout << "Person destructor name: " << m_name << std::endl;
}std::string& Person::getName()
{return m_name;
}void Person::setName(std::string& name)
{this->m_name = name;
}// main.cpp
void testSharedPtr2()
{// 1.shared_ptr<Person> ptr(new Person("Tom"));cout << ptr->getName() << endl;// 2.shared_ptr<Person> ptr2 = make_shared<Person>("Jerry");cout << ptr2->getName() << endl;}int main()
{testSharedPtr2();return 0;
}
二 make_unique
2.1 make_unique 是什么?
make_unique 是 c++14 加入标准库的,用于生成独占型指针 std::unique_ptr
2.2 make_unique 解决了什么问题?
用 make_unique 生成独占型指针代码量更少,符合现代 c++ 尽量避免使用 new 来构造的原则
2.3 make_unique 简单版实现
template<typename T, typename... Arg> // 可能有多个参数,所以用 ...
unique_ptr<T>
my_make_unique(Arg&& ... s) // 支持左值与右值,所以要用万能引用
{return unique_ptr<T>(new T(std::forward<Arg>(s)...));
}int main()
{unique_ptr<string> ptr = my_make_unique<string>("abcdd");cout << *ptr << endl;return 0;
}
2.4 make_unique 使用例子
很简单
int main()
{std::unique_ptr<std::string> a = std::make_unique("6666");return 0;
}
参考:C++11 make_shared - 简书 (jianshu.com)
从零开始写一个shared_ptr-make_shared源代码解析 - 知乎 (zhihu.com)
《Effective Modern C++》学习笔记之条款二十一:优先选用std::make_unique和std::make_shared,而非直接new - 知乎 (zhihu.com)