C++常见面试题 ——智能指针

什么是智能指针

智能指针是C++中可以自动管理内存的工具,它可以在不手动释放资源时,确保对象能够正确地被销毁。

C++ 中智能指针和指针的区别是什么?

智能指针是类模板,指针是C/C++中一个变量类型,存放的是内存地址。
在使用上,智能指针和裸指针都支持解引用*,->等操作,但智能指针提供包含许多额外的函数。

C++共有4种智能指针,包括:

  • unique_ptr:拥有独有对象所有权的智能指针,对于独有对象同一时间只有一个智能指针拥有它。
  • shared_ptr:拥有共享对象所有权的智能指针,通过引用计数跟踪引用特定对象。当引用计数为0时,释放拥有的对象。
  • weak_ptr:对shared_ptr的弱引用,访问时需要转换为shared_ptr;不控制对象的生命周期,配合shared_ptr用于解决shared_ptr循环引用问题。
  • auto_ptr:C++11前的智能指针,在C++17时已废弃,无需了解
void UseRawPointer()
{
    Song* pSong = new Song(L"Nothing on you");

    //Use pSong...
    delete pSong;
}

void UseSmartPointer()
{
    unique_ptr<Song> song2(new Song(L"Nothing on You"));

    // Use song2...
}

智能指针使用指向堆分配对象的原始指针进行初始化,初始化后,智能指针拥有原始指针。智能指针负责对原始指针的释放。
智能指针使用RAII机制,管理堆内存中的数据。当智能指针超过其作用域,或者抛出异常时,就会调用智能指针的析构函数,自动释放堆上的内存数据。

各个智能指针的特点

unique_ptr

unique_ptr通过指针占用并独占一个对象,并在unique_ptr离开作用域时释放该对象。
有两个场景会使用关联的删除器释放对象:

  • 销毁了管理的unique_ptr对象
  • 通过operator=或者reset()赋值给另一个unique_ptrs对象

unique_ptr只可移动,不支持复制。
unique_ptr与裸指针大小一致,在内存上无任何额外的消耗,和裸指针性能相当。

shared_ptr

支持复制、移动等操作。
内存占用上为裸指针的两倍,共有两个指针:

  • element_type* _M_ptr:指向所管理的资源
  • __shared_count<_Lp> _M_refcount:指向维护的引用计数,包括引用计数和弱引用计数

shared_ptr可以将unique_ptr移动过来。

shared_ptr的简单实现

#include <iostream>
#include <memory>

using namespace std;

template <typename T>
class MyShared_ptr {
public:
    MyShared_ptr(T* ptr = nullptr) {
        cout << __PRETTY_FUNCTION__ << endl;
        _ptr = ptr;
        _count = new int(1);
        _weakcount = new int(0);
    }

    MyShared_ptr(const MyShared_ptr& sharedPtr) {
        cout << __PRETTY_FUNCTION__ << endl;
        _count = sharedPtr._count;
        _ptr = sharedPtr._ptr;
        ++*_count;
        _weakcount = sharedPtr._weakcount;
    }

    MyShared_ptr& operator=(const MyShared_ptr& sharedPtr) {
        if (this == &sharedPtr) {	//处理自赋值的情况
            return *this;
        }
        --*_count;
        if (*_count == 0) {
            release();
        }

        _count = sharedPtr._count;
        _ptr = sharedPtr._ptr;
        ++*_count;
        _weakcount = sharedPtr._weakcount;
        return *this;
    }

    T* get() {
        return _ptr;
    }

    int use_count() {
        return *_count;
    }

    T& operator*() {
        return *_ptr;
    }

    T* operator->() {
        return _ptr;
    }

    ~MyShared_ptr() {
        cout << __PRETTY_FUNCTION__ << endl;
        --*_count;
        cout << "_count: " << *_count << endl;
        if (*_count == 0) {
            release();
        }
    }

    void release() {
        delete _count;
        delete _ptr;
        delete _weakcount
        _count = nullptr;
        _ptr = nullptr;
        _weakcount = nullptr;
    }
	
	//注意,在STL的正规实现中,引用计数和弱引用计数在一个结构体中,shared_ptr保存一个指向该结构体的指针,这里这么写是为了描述简单
    int *_count;		//引用计数
    int *_weakcount;	//弱引用计数
    T *_ptr;			//智能指针维护的控制块
};

在上述的实现中,基本描述了智能指针的组成模块,包括引用计数、弱引用计数、资源的维护。当然在标准STL中的shared_ptr,包含有许多额外的内容,这里就不全部实现了。

shared_ptr指针的循环引用及解决方法 —— weak_ptr

shared_ptr指针的循环引用问题指,两个或多个shared_ptr互相引用,导致shared_ptr的引用计数无法到0,而出现内存泄露的情况。
weak_ptr就是被设计解决该问题的工具。

weak_ptr的简易实现

首先下面给出简易的weak_ptr实现,用于和MyShared_ptr进行配合使用。

/* weak_ptr的典型实现为存储两个指针:
* 指向控制块的指针,即这里的_ptr;
* 指向来源shared_ptr的存储指针;
*/
template <typename T>
class MyWeak_ptr {
public:
    T* _ptr;
    MyShared_ptr<T> *_shared;

    MyWeak_ptr() {
        _ptr = nullptr;
        _shared = nullptr;
    }
    MyWeak_ptr(MyShared_ptr<T>& shared): _ptr(shared.get()), _shared(&shared) {
        cout << __PRETTY_FUNCTION__ << endl;
        ++*shared._weakcount;       //只增加shared_ptr对象中的弱引用计数
    }

    MyWeak_ptr<T>& operator=(const MyShared_ptr<T>& shared) {
        cout << __PRETTY_FUNCTION__ << endl;
        _ptr = shared._ptr;
        _shared = const_cast<MyShared_ptr<T> *>(&shared);
        ++*_shared->_weakcount;     //只增加shared_ptr对象中的弱引用计数

        return *this;
    }

    bool expired() {
        if (_shared == nullptr)
            return false;
        return _shared->use_count() > 0;
    }

    MyShared_ptr<T> lock() {
        if (_shared == nullptr) {
            return MyShared_ptr<T>(nullptr);
        }
        return *_shared;
    }


    ~MyWeak_ptr() {
        cout << __PRETTY_FUNCTION__ << endl;
    }
};

以上就是weak_ptr的简易实现,下面描述循环引用问题。

shared_ptr的循环引用问题
//引用循环问题
class Son1;
class Father1 {
public:
    MyShared_ptr<Son1> _son;        
    Father1() {
        cout << __PRETTY_FUNCTION__ << endl;
    }
    ~Father1() {
        cout << __PRETTY_FUNCTION__ << endl;
    }
};

class Son1 {
public:
    MyShared_ptr<Father1> _father;       //注意:此处为shared_ptr
    Son1() {
        cout << __PRETTY_FUNCTION__ << endl;
    }
    ~Son1() {
        cout << __PRETTY_FUNCTION__ << endl;
    }
};

int main()
{
    Father1 *fa = new Father1();
    Son1 *so = new Son1();
    MyShared_ptr<Father1> father(fa);
    MyShared_ptr<Son1> son(so);
    father->_son = son;
    son->_father = father;

    cout << "father count: " << father.use_count() << endl;
    cout << "son count: " << son.use_count() << endl;

    return 0;
}

执行上述代码,可以获得一下输出:

$ ./test_shared_ptr.exe 
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Son1]
Father1::Father1()
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Father1]
Son1::Son1()
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Father1]
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Son1]
father count: 2
son count: 2
MyShared_ptr<T>::~MyShared_ptr() [with T = Son1]
_count: 1           //程序结束后,Son1的引用还为1,无法被释放
MyShared_ptr<T>::~MyShared_ptr() [with T = Father1]
_count: 1           //程序结束后,Father1的引用还为1,无法被释放

可以看到,在程序结束之后,并没有调用Father1和Son1的析构函数,这就是由于Father1和Son1中的shared_ptr循环引用导致的内存泄露问题。

借助weak_ptr解决循环引用问题

接下来看如何使用weak_ptr来解决循环引用问题。

class Son2;
class Father2 {
public:
    MyShared_ptr<Son2> _son;
    Father2() {
        cout << __PRETTY_FUNCTION__ << endl;
    }
    ~Father2() {
        cout << __PRETTY_FUNCTION__ << endl;
    }
};

class Son2 {
public:
    MyWeak_ptr<Father2> _father;         //注意:此处为weak_ptr
    Son2() {
        cout << __PRETTY_FUNCTION__ << endl;
    }
    ~Son2() {
        cout << __PRETTY_FUNCTION__ << endl;
    }
};

int main()
{
    Father2 *fa = new Father2();
    Son2 *so = new Son2();
    MyShared_ptr<Father2> father(fa);
    MyShared_ptr<Son2> son(so);
    father->_son = son;
    son->_father = father;

    cout << "father count: " << father.use_count() << endl;
    cout << "son count: " << son.use_count() << endl;

    return 0;
}

注意,Son2中的Father为weak_ptr,执行上述代码,可得到一下打印:

$ ./test_shared_ptr.exe 
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Son2]
Father2::Father2()
Son2::Son2()
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Father2]
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Son2]
MyWeak_ptr<T>& MyWeak_ptr<T>::operator=(const MyShared_ptr<T>&) [with T = Father2]
father count: 1
son count: 2
MyShared_ptr<T>::~MyShared_ptr() [with T = Son2]
_count: 1
MyShared_ptr<T>::~MyShared_ptr() [with T = Father2]
_count: 0
Father2::~Father2()         //释放Father2
MyShared_ptr<T>::~MyShared_ptr() [with T = Son2]
_count: 0
Son2::~Son2()               //释放Son2
MyWeak_ptr<T>::~MyWeak_ptr() [with T = Father2]

可以看到,在程序执行结束后,分别执行了Father2和Son2的析构函数,这里的Father2和Son2被正确释放了,解决了之前的循环引用导致内存泄漏的问题。

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值