使用智能指针就是为了克服裸指针的一系列缺点:
1、裸指针在声明中并没有指出指向的是单个对象还是一个数组。
2、裸指针在声明中也没有提示在使用完指向的对象之后,是否需要析构它。即在声明中看不出指针是否拥有其指向的对象。
3、即使知道指向的对象,也不可能知道怎样析构才是合适的。是调用delete还是放到专门的用于析构的函数里面。
4、即使知道需要使用delete,匹配对像“delete”和数组“delete[]”也会存在未定义行为。
5、上述都清除,要保证析构在所有代码路径上都执行一次仍然有难度。只要有一条路径未执行,则会导致资源泄露,执行多次也会产生未定义行为。
6、无法检测出指针是否空悬,即它所指向的内存是否已经不再持有指针本应该指向的对象,若一个对象已经被析构,而某指针依然指向它,则会产生空悬指针。
智能指针: 对裸指针进行包装。通过保证对象在适当的时机被析构来防止资源泄露。
1、unique_ptr是小巧、高效、具备只移类型的智能指针,对托管资源实施专属所有权语义。
2、 指针析构时,默认是通过unique_ptr内部的裸指针实施delete完成。当然也可以自定义析构函数,例如:
class A{};
auto del = [](A* a) {//自定义析构函数
//log
delete a;
};
unique_ptr<A, decltype(del)> makeA() { }//使用自定义析构函数
3、 使用unique_ptr转换成shared_ptr很容易实现,在工厂模式常用作工厂函数的返回类型。
unique_ptr<A, decltype(del)> makeA() { }
shared_ptr<A> sh = makeA();
1、shared_ptr提供方便的手段,实现了任意资源在共享所有权语义下进行生命周期管理的垃圾回收。通过引用计数来管理共享资源,当 引用计数为0时,调用析构函数释放资源。
引用计数: 一个与资源关联的值,用来记录跟踪指向该资源的shared_ptr数量。会带来一下性能影响:
注:每个shared_ptr管理的对象都有一个控制块,除包含引用计数外,还包含自定义析构器的一个复制、弱计数、其他数据(自定义删除器、分配器等)
2、shared_ptr会带来控制块的开销,并要求原子化引用计数操作。控制块创建遵循的规则:
从同一个裸指针出发构造不止一个shared_ptr会出现未定义的行为。
auto pw=new Widget;
shared_ptr<Widget> s1(pw);//创建第一个控制块
shared_ptr<Widget> s2(pw);//创建第二个控制块
此时pw就要两个引用计数,在第二次析构时会出现未定义行为,可以在第一次使用new Widget,第二次则s2(s1)调用复制构造函数,避免这个问题。
3、当希望一个托管到shared_ptr的类能安全地由this指针创建一个shared_ptr时,将为继承而来的基类提供一个模板——enable_shared_from_this(基类模板)。
class Widget:public enable_shared_from_this<Widget>{
public:
void process{
//Widgets.emplace_back(this);//出现未定义行为:在已经指向Widget类型的对象的成员函数外部再套了一层shared_ptr.
Widgets.emplace_back(shared_from_this());
}
};
weak_ptr:像shared_ptr那样运作,又不影响其指向的引用计数,即可以处理指向的对象可能已被析构的情况,是shared_ptr的一种补充。
weak_ptr是否失效的试验:
auto spw=make_shared<Widget>();
weak_ptr<Widget> wpw(spw);
shared_ptr<Widget> spw1=wpw.lock();//若wpw失效,则spw1为空
//auto spw1=wpw.lock();//auto版本
//shared_ptr作为实参构造
shared_ptr<Widget> spw3(wpw);//若wpw失效,则抛出bad_weak_ptr异常
weak_ptr可被用到缓存、观察者模式、避免指针环路的场景。
可以通过使用weak_ptr的expired()成员函数来检查它指向的资源是否有效:
weak_ptr<Widget> wp;
if(!wp.expired()){//wp指向的对象时有效的
shared_ptr<Widget> spw=wp.lock();//weak_ptr不提供指针的解引用操作(* ->),若要使用它观察的资源,必须先调用lock()成员函数获取一个shared_ptr实例。
}
C++14才支持make_unique.
使用make创建智能指针的对比:
1、代码冗余
auto upw1(make_unique<Widget>());
unique_ptr<Widget> upw2(new Widget);
auto spw1(make_shared<Widget>());
shared_ptr<Widget> spw2(new Widget);
使用new版本创建对象的类型重复写了两遍,代码冗余。
2、异常安全
定义一
//依据某种优先级处理Widget
void processWidget(sharde_ptr<Widget> spw,int priority)
优先级计算由函数int computePriority() 实现。
则调用函数:
processWidget(shared_ptr<Widget>(new Widget),computePriority));
会发生潜在的资源泄露。
运行期:传递给函数的实参必须在函数调用发起之前完成评估求值,因此先new Widget,再shared_ptr,再computePriority。
编译期:new Widget必须在shared_ptr之前,但computePriority可在任意顺序。
**极端情况下:**先new Widget,再computePriority,最后shared_ptr,一旦运行期computePriority发送异常,第一步分配的Widget就会被泄露。而make_shared则不会。
3、性能提升。
使用new会进行两次内存分配(多了new这一次),make只有一次。
不适用make的场景
1、希望自定义析构器的智能指针
2、由于make会向对象的构造函数完美转发形参,使用的是圆括号,因此若需要使用大括号初始化对象,则需要用new。
特殊地,针对make_shared不适用的场景还包括:
1、自定义内存管理的类
2、内存紧张的类,对象很大
3、存在weak_ptr