【C++面试篇】智能指针

124 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露,二次释放,程序发生异常时内存泄露等问题,使用智能指针能更好的管理堆内存。

C++的智能指针是一个类,用于存储指向动态分配对象的指针,负责自动释放该对象,防止内存泄露以及多次释放同一块内存空间

C++的智能指针有四种:shared ptr、weak ptr、unique ptr、auto ptr,auto_ptr 在 C++11已被摒弃,在C++17中已经移除不可用,下面将对这四种智能指针做详细介绍

shared_ptr

采用引用计数,允许多个智能指针指向同一个对象,在内部会维护一份引用计数,用于记录资源被多少个对象共享,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放该对象

引用计数是线程安全的,但是对象的读取需要加锁

  • 因为shared_ptr有两个成员:指向资源的指针和指向引用的指针,shared_ptr可能在增加引用计数后改变指针方向

相关操作

初始化

img

shared_ptr直接构造和使用make_shared构造

  • shared_ptr构造函数会执行两次内存申请,make_shared执行一次:shared_ptr在实现的时候需要有计数器和指针,当使用shared_ptr构造函数时会先申请数据的内存,再申请控制块;使用make_shared的时候是将数据和控制块一起申请
  • 两次申请可能会导致内存泄露:new先申请内存后发生了异常,导致数据指针没有传递给shared_ptr没办法释放。使用make_ptr将数据和控制块一起申请就可以保证内存安全被释放

获取原始指针

img

获取原始指针是一种危险的行为:保存为裸指针可能会变成空悬指针,保存为shared_ptr则产生了独立指针

img

img

使用delete或保存为shared_ptr会导致对一块内存delete两次

指定删除器

使用shared_ptr管理非new对象或没有析构函数的类时需要为其传递合适的删除器

img

管理动态数组时需要指定删除器,shared_ptr的默认删除其不支持数组对象

img

注意问题

不要使用一个原始指针初始化多个shared_ptr

img

不要在函数实参中创建shared_ptr

  • C++函数实参的计算顺序在不同编译器下约定不同,一般从右到左
  • function(shared_ptr<int>(new int),g()可能会先new int再调用g(),如果g()异常退出,但是shared_ptr还没有创建,会导致int内存泄露

通过shared_from_this返回this的shared_ptr

使用同一个指针this构造了两个智能指针,会导致this的内存重复释放

img

应该让目标类继承enable_shared_from_this类,使用基类的成员函数shared_from_this返回this的shared_ptr

img

循环引用

两个对象互相使用一个shared_ptr成员变量指向对方,会造成智能指针不能调用析构函数导致两个对象都无法释放

img

自己实现shared_ptr

template<typename T>
class shared_ptr{
private:
    T *ptr;
    long *use_count;
public:
    shared_ptr(T* p);
    shared_ptr(const shared_ptr<T> & orig);
    ~shared_ptr();
    shared_ptr<T>& operator=(const shared_ptr<T> &orig);
    T operator*();
    T* operator->();
    long getcount();
};
​
template<typename T>
shared_ptr<T>::shared_ptr(T *p){
    ptr = p;
    try{
        use_count = new long(1);
    }catch(...){
        delete ptr;
        ptr = nullptr;
        p=nullptr;
        use_count = nullptr;
    }
}
template<typename T>
shared_ptr<T>::shared_ptr(const shared_ptr<T> &orig){
    use_count = orig->use_count;
    ptr = orig->ptr;
    ++(*use_count);
}
template<typename T>
shared_ptr<T>& shared_ptr<T>::operator=(const shared_ptr<T> &orig){
    if(--(*use_count)==0){
        delete ptr;
        ptr = nullptr;
        delete use_count;
        use_count = nullptr;
    }
    ++(*orig->use_count);
    ptr = orig->ptr;
    use_count = orig->use_count;
    return *this;
}
​
template<typename T>
shared_ptr<T>::~shared_ptr(){
    if(--(*use_count)==0){
        delete ptr;
        ptr = nullptr;
        delete use_count;
        use_count = nullptr;
    }
}
template<typename T>
T shared_ptr<T>::operator*(){ return *ptr;}
​
template<typename T>
T* shared_ptr<T>::operator->(){ return ptr;}
​
template<typename T>
long shared_ptr<T>::getcount(){return *use_count;}

weak_ptr

弱引用:指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,只引用,不计数

weak_ptr没有重载操作符*和->,因为不共享指针,不能操作资源,主要是为了监视shared_ptr管理的资源是否存在以及解决循环引用问题

img

img

weak_ptr返回this指针指向shared_ptr

endable_shared_from_this类中有一个weak_ptr,用于观察this智能指针,调用shared_from_this会调用内部的weak_ptr的lock方法,将观察到的shared_ptr返回

解决循环引用问题

将两个对象的任意一个成员变量改为weak_ptr

unique_ptr

独享所有权的智能指针,不允许其他智能指针共享其内部的指针,不允许通过赋值将unique_ptr赋值给另一个unique_ptr

但是可以通过move将一个 unique_ptr对象转移给另一个unique_ptr对象,原unique_ptr就不再拥有原来指针的所有权的

make_unique是c++14的unique_ptr<int> p = make_unique<int>(10);

unique_ptr需要确定删除器类型img

用同一个指针给多个unique_ptr初始化是危险操作,会导致资源重复释放

auto_ptr

C++11废弃了auto_ptr,其允许强制剥夺所有权, 会存在野指针风险,C++11中使用unique_ptr取代

当使用auto_ptr拷贝或赋值时,原对象会被置空:将对象赋给新的变量,释放原对象的内存,将原指针置为空