C++11特性总结(6)-智能指针的使用和注意事项

1,896 阅读8分钟

头文件:<memory>

shared_ptr

作用:通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。下列情况之一出现时销毁对象并解分配其内存:

  • 最后剩下的占有对象的 shared_ptr 被销毁;
  • 最后剩下的占有对象的 shared_ptr 被通过 operator= 或 reset() 赋值为另一指针

shared_ptr的内存占用是裸指针的两倍。因为除了要管理一个裸指针外,还要维护一个引用计数

shared_ptr的使用场景:

  1. 有多个使用者共同使用同一个对象,而这个对象没有一个明确的拥有者;
  2. 对象的复制操作很费时,同时又需要在函数间传递对象, 我们往往会选择传递指向这个对象的指针来代替传递对象本身

shared_ptr的创建

  1. 用make_shared(首选,速度快)
shared_ptr<int> p = make_shared\<int\>(42) //创建一个智能指针指向int=42的变量
  1. 直接初始化
shared_ptr<int> p(q) //q可以是内置指针或智能指针
  1. 自定义类型的初始化
shared_ptr<int> p(q,d) //q是指针,d是自定义的删除器,用来代替delete

shared_ptr操作

  1. unique():如果该内存只有一个智能指针指向返回true,否则false

  2. use_count():返回该内存有几个智能指针指向

  3. reset(q,d):重置智能指针接管q指针的内存,指定d为删除器。如果q缺省,代表智能指针置为空

  4. 有* 和->运算符

unique_ptr

作用独享一个内存空间的智能指针,不允许其他智能指针指向,所以不能被拷贝。除了特殊情况:函数返回值可以拷贝unique_ptr,因为这种情况下编译器知道返回的局部对象将要被销毁,所以进行移动拷贝构造。

unique_ptr在默认情况下和裸指针的大小是一样的。所以内存上没有任何的额外消耗,性能是最优的。

unique_ptr的创建

  1. 用make_unique
unique_ptr<int> p = make_unique<int>(42); //创建一个智能指针指向int=42的变量
  1. 直接初始化
unique_ptr<int> p(q) //q可以是内置指针或智能指针
  1. 自定义类型的初始化: 给unique_ptr传递删除器:和shared_ptr不同的是,需要在尖括号内传递类型参数
1. 传递一个操作函数
如先定义end_connection() 
unique_ptr<connection, decltype(end_connection)*>  p(&c , end_connection)  

2. 传递一个函数对象
如定义del函数对象
unique_ptr<int,del> p(&num);

两者等价

unique_ptr操作

  1. release():返回一个指向被管理对象的内置指针,放弃unique_ptr所有权(注意并不释放空间)使用此函数后,程序员需要承担指针空间的释放
  2. reset(q):q为内置指针,若q为缺省,则释放unique_ptr指向对象,否则将unique_ptr指向q指向的对象
  3. 有* 和->运算符

weak_ptr

作用:是对象的弱引用,weak_ptr指向和shared_ptr指向的对象时,shared_ptr无需对weak_ptr进行计数,即意味着:weak_ptr指向的空间随时可能被他人删除。主要是用于解决shared_ptr的环状引用问题。没有* 和->运算符

创建:unique_ptr<int> p(q)

操作:

  1. use_count():返回管理该对象的 shared_ptr 对象数量
  2. expired():检查被引用的对象是否已删除,返回true/false
  3. lock():创建管理被引用的对象的shared_ptr,若expired为true则返回一个空shared_ptr。weak_ptr是无法直接访问对象的,必须通过lock()获得它的shared_ptr,进行访问,而且要判断是否为空:if( shared_ptr p = weak.lock())
  4. reset():无参数,释放被管理对象的所有权。

智能指针和动态数组

  1. 使用unque_ptr:
unque_ptr <int[ ]> u(new int[10]); //运行结束会调用delete []p
  1. 使用shared_ptr:需要一个删除器
shared_ptr <int[ ],del> u(new int[10]); //del为删除器,如果没有定义删除器的话,就会按照delete p来删除一个动态数组,将会是错误的

unque_ptr支持下标访问,但是shared_ptr不支持。

所有的智能指针都不支持指针运算(p+i)访问元素,因此需要get()返回一个内置指针才可以进行指针运算。

智能指针使用注意事项

  1. 智能指针的构造函数是explicit即显式的,不能通过内置指针类型隐式转换为一个智能指针
shared_ptr<int> p = new int(1);  //error,试图转换
shared_ptr<int> p(new int(1));  //yes,直接初始化

所以在函数return的时候,我们如果返回类型是智能指针,我们也不可以return一个内置指针,因为这两者无法隐式转换。而应该:

return shared_ptr<int>(new int (num));  //其中num是一个int变量
  1. 不要混用内置指针和智能指针
int* x = new int();
//假设func是接收shared_ptr参数的一个函数
func(shared_ptr<int>(x)); //使用内置指针直接初始化shared_ptr
x=nullptr;//x指针原本的内存空间以及交由智能指针管理,自己不应该再使用

这样子是合法的,但是会导致func函数执行完之后x指针指向的空间被释放。因为shared_ptr不知道还有内置指针存在。 所以一旦我们把内置指针的内存交给智能指针管理,内置指针就应该不再被使用

  1. 智能指针提供了get()操作可以返回一个内置指针
  • 使用这个内置指针的时候,一定不能让内置指针把内存delete,否则shared_ptr会再次delete
  • 不能用这个内置指针再给其他shared_ptr赋值或初始化,否则其他shared_ptr并不知道还有shared_ptr存在,因为他们的中介是内置指针,无法和智能指针通信。
shared_ptr<int> p(new int(2))
q = p.get()
{
    shared_ptr<int> s(q)  //s离开生存期之后会自动delete q指向的内存
}
//此时p、q已经变成空
  1. 不能用相同的内置指针初始化或赋值给多个智能指针(否则就会出现第3点相同的错误)
  2. 智能指针是异常安全的

能够保证在程序发生异常的时候,无需执行delete语句,对象也会被析构。(可以把shared_ptr认为是一个局部变量,它只要离开生存期就会被释放)

  1. 智能指针还能用于其他资源的管理,用于达到异常安全的目的

文件的异常情形下关闭、套接字的异常情形下关闭

我们也可以把对应资源的地址赋给智能指针,并且给它一个定义好的delete删除器,不管是在发生异常时还是正常的运行时,如果对应资源不再被需要,则智能指针会自动调用delete删除器释放对应的资源

#include <iostream>
#include <memory>
#include <fstream>
using namespace std;
void fun(fstream& f)
{
    //创建一个shared_ptr,把f文件的地址赋给它,并且通过lambda表达式传递给shared_ptr一个删除器,用于关闭文件
    shared_ptr<fstream> p(&f , [](fstream* f){  f->close(); });
    throw -1;  //程序出现异常
}
int main()
{
    fstream f("C:\\Users\\Allen\\1.txt");  //打开文件
    cout<<f.is_open()<<endl;  //cout:true,文件已打开
    try
    {
        fun(f);
    }
    catch(...)
    {
        cout<<f.is_open()<<endl; //cout:false,文件已经关闭了
    }
}

可以看到,通过shared_ptr的管理,在出现异常的情况下,我们也不需要关心资源的释放问题。

  1. 不要把this指针交给智能指针管理

否则成员函数运行结束的时候会自动销毁智能指针管理的*this对象,即对象被销毁。而且在对象离开本身的作用域内又会被重复销毁一次。

class A
{
public:
    void fun()
    {
        unique_ptr<A> s(this);  
    }
    ~A()
    {
        cout<<"Del"<<endl;
    }
};
int main()
{
    A a;
    a.fun();//一旦使用fun()函数,程序就会出错
    //若屏蔽a.fun()语句,程序会正常输出:Del
}

若确实有需要在类内获取this的智能指针,则需要让目标对象继承enable_shared_from_this<T>模板类,并使用shared_from_this()来获得this指针的shared_ptr对象,并且把现有目标对象交给shared_ptr管理。这样子可以使得多个管理this对象的智能指针引用计数同步。

class A:public enable_shared_from_this<A>
{
public:
    shared_ptr<A> fun()
    {
        return shared_from_this();  
    }
    ~A()
    {
        cout<<"Del"<<endl;
    }
};
int main()
{
    shared_ptr<A> a(new A);  //把现有目标对象交给shared_ptr管理
    shared_ptr<A> s=a->fun();   //从类内又获取了this指针的shared_ptr
    cout<<a.use_count();   //cout:2 。多个引用计数同步了

    //cout:Del。析构一次
}
  1. 为智能指针初始化的最好方式是在申请内存的时候直接交由智能指针管理,而不要经过内置指针再二次赋值
//直接管理:推荐
shared_ptr<Thing> p1(new Thing);
shared_ptr<Thing> p2(make_shared<Thing>());
//间接管理:不推荐
Thing* pt = new Thing;  //留下了隐患,有可能再智能指针已经释放了空间的情况下会不小心使用pt访问对象
shared_ptr<Thing> p1(pt);

9.能用unique_ptr管理的对象,不要使用shared_ptr/weak_ptr