使用std::unique_ptr管理具备专属所有权的资源
1、std::unique_ptr 是只移型别。
2、std::unique_ptr 默认析构通过delete实现,但可以设置自定义析构器。
auto delInvmt = [](Investment* pInvestment){
makeLogEntry(pInvestment);
delete pInvestment;
};
template<typename... Ts>
std::unique_ptr<Investment,decltype(delInvmt)> makeInvestment(Ts&&... params){
std::unique_ptr<Investment,decltype(delInvmt)> pInv(nullptr,delInvmt);
if(...){
pInv.reset(new ...(std::forward<Ts>(params)...));
} else if(...){
...
}
return pInv;
}
3、std::unique_ptr<T,deleter> 根据自定义删除器类型,std::unique_ptr属于不同种类型。而std::shared_ptr相对更具弹性,即使拥有不同的删除器,也属于同种类型。
auto loggingDel = [](Widget *pw){
makeLogEntry(pw);
delete pw;
}
std::unique_ptr<Widget, decltype(loggingDel)> upw(new Widget,loggingDel);
std::shared_ptr<Widget, decltype(loggingDel)> spw(new Widget,loggingDel);
auto customDeleters1 = [](Widget *pw){ ... };
auto customDeleters2 = [](Widget *pw){ ... };
std::shared_ptr<Widget> pw1(new Widget,customDelete1);
std::shared_ptr<Widget> pw2(new Widget,customDelete2);
std::vector<std::shared_ptr<Widget>> vpw{pw1,pw2};
4、有状态的删除器和采用函数指针实现的删除器会增加std::unique_ptr型别的对象尺寸。
5、std::unique_ptr可以转换成std::shared_ptr。
std::shared_ptr<Investment> sp = makeInvestment(arguments);
使用std::shared_ptr管理具备共享所有权的资源
1、针对std::shared_ptr而言,移动比复制要快:复制要求递增引用计数,而移动则不需要。
2、自定义析构器不会改变std::shared_prt的大小。无论析构器是怎样的型别,std::shared_ptr对象的尺寸都相当于裸指针的两倍,例外情况不详细描述(涉及自定义内存分配器相关)。
3、控制块的创建规则如下:
(1)std::make_shared总是创建一个控制块。
(2)从具备专属所有权的指针出发构造一个std::shared_ptr时,会创建一个控制块。
(3)当std::shared_ptr构造函数使用裸指针作为实参来调用时,它会创建一个控制块。
Note:上述规则导致这样的一个后果,从同一个裸指针出发构造不止一个std::shared_ptr的话,会导致被指涉的的对象将会有多重的控制块。多重的控制块意味着多重的引用计数,而多重的引用计数意味着该对象将被析构多次(从而导致未定义行为)。
auto pw = new Widget; // pw是个裸指针
std::shared_ptr<Widget> spw1(pw,loggingDel); //为*pw创建一个控制块
std::shared_ptr<Widget> spw2(pw,loggingDel); //为*pw创建了第二个控制块
4、std::shared_ptr不能够直接赋值给std::unique_ptr
优先选用std::make_unique和std::make_shared,而非直接使用new
原因有二
1、避免代码冗余,使用new需要重复编写类型T,而make系列函数不需要。
std::unique_ptr<T> upw1(new T);
auto upw2 = std::make_unique<T>();
std::shared_ptr<T> spw1(new T);
auto spw2 = std::make_shared<T>();
2、异常安全
void processWidget(std::shared_ptr<T> spw,int priority);
processWidget(std::shared_ptr<T>(new T),computePriority());
上述代码会发生内存泄露的原因在于编译器从源代码到目标代码的编译有关。在运行期,传递给函数的实参必须在函数调用被发起之前完成评估求值。
即:
(1)new Widget先创建。
(2)由new产生的裸指针的托管对象std::shared_prt的构造函数必须执行。
(3)computePriority必须运行。
上述三个步骤的顺序不一定固定,编译器有可能产生如下代码
(1)实施 new Widget
(2)执行computePriority
(3)运行std::shared_ptr的构造函数
如果computePriority产生了异常了,那么第一步动态分配的结果就会被泄露。而使用make_shared可以避免该问题。
processWidget(std::make_shared<Widget>(),computePriority());
3、性能提升,会让编译器利用更简洁的数据结构产生更小更快的代码。
std::shared_ptr<Widget> spw(new Widget);
auto spw = std::make_shared<Widget>();
new会引发两次内存分配,一次是分配对象,一次是分配控制块。
make_shared只分配一次,分配单块内存(既保存与其相关联的控制块又保存其对象)。
Note:不适于使用make系列函数的场景
1、自定义析构器
2、期望直接传递大括号初始化物。
Note:不建议使用make系列函数的额外场景
1、自定义内存管理类
2、内存紧张的系统、非常大的对象、以及存在比指涉到相同对象的std::shared_ptr生存期更久的std::weak_ptr。
使用Pimpl习惯用法时,将特殊成员函数的定义放到实现文件中(仅适用于std::unique_ptr,不适用于std::shared_ptr)。
Pimpl 意为 pointer to implementation 指向实现的指针。
Note:一个已声明但未定义的型别称为非完整型别。
Pimpl第一部分:是声明一个指针型别的数据成员,指涉到一个非完整型别。
class Widget{
public:
Widget();
...
private:
struct Impl;//声明实现结构体
Impl *pImpl;//指向实现的指针
};
第二部分:动态分配和回收持有从前在原始类里的那些数据成员的对象,而分配和回收代码则放在实现文件中。
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl{
std:;string name;
std::vector<double> data;
Gadget g1,g2,g3;
};
Widget::Widget():pImpl(new Impl){}
Widget::~Widget() {delete pImpl;}
上述代码是c++98的代码。
c++14代码如下所示:
class Widget{
public:
Widget();
...
private:
struct Impl;//声明实现结构体
std::shared_ptr<Impl> *pImpl;//指向实现的指针
};
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl{
std:;string name;
std::vector<double> data;
Gadget g1,g2,g3;
};
Widget::Widget():pImpl(std::make_shared<Impl>()){}
上述代码通不过编译的原因在于:在实施delete运算符之前,典型的实现会使用c++11中的static_assert去确保裸指针未指涉到非完整类型。因此解决的方法是只要保证在生成析构std::unique_ptr Widget::T代码处,Widget::Impl是个完整型别即可。
class Widget{
public:
Widget();
~Widget();
...
private:
struct Impl;//声明实现结构体
std::shared_ptr<Impl> *pImpl;//指向实现的指针
};
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl{
std:;string name;
std::vector<double> data;
Gadget g1,g2,g3;
};
Widget::Widget():pImpl(std::make_shared<Impl>()){}
Widget::~Widget(){} = default;