【C++】智能指针

橙色

不带引用计数的智能指针auto_ptr,scoped_ptr,unique_ptr
带引用计数的智能指针shared_ptr,weak_ptr

智能指针

具体可参考该篇文章。下面是对其的一些补充:

说是智能指针,其实智能指针就是一个对象,而真正的指针在它的成员变量里,通过对象出作用域会自动调用析构函数的特点来做到一个对指针及时释放的处理。

为什么说p是裸指针呢?因为它的前面带了*号,又通过new CSmartPtr(new int)在堆上进行了创建,代表它是一个CSmartPtr类型的指针而不是一个对象了

如这段代码CSmartPtr *p = new CSmartPtr(new int);大家应该能看出来,这里定义的p虽然是智能指针类型,但它实质上还是一个裸指针,因此p还是需要进行手动delete,又回到了最开始裸指针我们面临的问题。

为什么智能指针的运算符的重载函数返回的是一个引用呢,因为一般用到 * 的话像ptr = 20,就是需要对指针所指向的地址中所存储的值进行改变。如果不返回引用的话,就无法改变该重载函数所返回的指针指向的地址中所存储的值

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

不带引用计数的智能指针
auto_ptr:c++库里面
C++11新标准:
scoped_ptr
unique_ptr

auto_ptr:

auto_ptr智能指针不带引用计数,那么它处理浅拷贝的问题,是直接把前面的auto_ptr都置为nullptr,只让最后一个auto_ptr持有资源。(所以auto_ptr不要使用在容器中,因为如下图,在拷贝容器的时候,容器中的元素也会进行拷贝,这样就会导致原容器中的元素中的成员变量也就是指针变成空指针)

int main()
{
	vector<auto_ptr<int>> vec;
	vec.push_back(auto_ptr<int>(new int(10)));
	vec.push_back(auto_ptr<int>(new int(20)));
	vec.push_back(auto_ptr<int>(new int(30)));
	// 这里可以打印出10
	cout << *vec[0] << endl;
	vector<auto_ptr<int>> vec2 = vec;
	/* 这里由于上面做了vector容器的拷贝,相当于容器中
	的每一个元素都进行了拷贝构造,原来vec中的智能指针
	全部为nullptr了,再次访问就成访问空指针了,程序崩溃
	*/
	cout << *vec[0] << endl;
	return 0;
}

scoped_ptr

该智能指针由于私有化了拷贝构造函数operator=赋值函数,因此从根本上杜绝了智能指针浅拷贝的发生,所以scoped_ptr也是不能用在容器当中的,如果容器互相进行拷贝或者赋值,就会引起scoped_ptr对象的拷贝构造和赋值,这是不允许的,代码会提示编译错误。

unique_ptr

unique_ptr有一点和scoped_ptr做的一样,就是去掉了拷贝构造函数和operator=赋值重载函数,禁止用户对unique_ptr进行显示的拷贝构造和赋值,防止智能指针浅拷贝问题的发生。

但是unique_ptr提供了带右值引用参数的拷贝构造和赋值,也就是说,unique_ptr智能指针可以通过右值引用进行拷贝构造和赋值操作。

带引用计数的智能指针shared_ptr,weak_ptr

如果你想要使用弱智能指针来调用其指向对象的成员函数或者变量,那首先应该把它提升为强智能指针,且判断是否为空(即是否提升成功,为空则提升失败了,说明在提升过程中该弱智能指针所指向的对象已经被释放了)

代码不完整,大概意思如此

weak_ptr<A>_ptra;
shared_ptr<A> ps = _ptra.lock();
if(ps!=nullptr)
{
	ps->testA();
}	

智能指针的交叉引用(循环引用)问题

本文最上方推荐的文章中有详细介绍。简而言之,就是分别用了两个强智能指针指向了两个对象,两个对象中又有强智能指针类型的成员变量指向了对方的对象。这就导致两个对象的引用计数变为了2。当出了main函数作用域后,两个对象析构,分别给A对象和B对象的引用计数从2减到1,达不到释放A和B的条件(释放的条件是A和B的引用计数为0),从而导致资源无法释放。

那怎么解决这个问题呢?请注意强弱智能指针的一个重要应用规则:定义对象时,用强智能指针shared_ptr,在其它地方引用对象时,使用弱智能指针weak_ptr。

多线程访问共享对象问题

线程A和线程B访问一个共享的对象,如果线程A正在析构这个对象的时候,线程B又要调用该共享对象的成员方法,此时可能线程A已经把对象析构完了,线程B再去访问该对象,就会发生不可预期的错误。

该问题同样可以通过巧妙地运用智能指针来解决。

实现带引用计数的智能指针

#include <iostream>
#include <memory>
using namespace std;

// 对资源引用计数的类
template <typename T>
class RefCnt
{
public:
    RefCnt(T *ptr = nullptr)
        : mptr(ptr)
    {
        if (mptr != nullptr)
            mcount = 1;
    }
    void addRef() { mcount++; }//增加资源的引用计数
    int delRef() { return --mcount; }

private:
    T *mptr;
    int mcount;
};

template <typename T>
class CSmartPtr
{
public:
    CSmartPtr(T *ptr = nullptr)
        : mptr(ptr)
    {
        mpRefCnt = new RefCnt<T>(mptr);
    }
    ~CSmartPtr()
    {
        if(0==mpRefCnt->delRef())
        {
            delete mptr;
            mptr = nullptr;
        }      
    }

    T &operator*() { return *mptr; }
    T &operator->() { return mptr; }

    CSmartPtr(const CSmartPtr<T> &src)
        : mptr(src.mptr), mpRefCnt(src.mpRefCnt)
    {
        if (mptr != nullptr)
            mpRefCnt->addRef();
    }

    //赋值运算符,就是让本来指向该资源的指针指向了另一个对象,那么原本指向的资源的引用计数就要减一
    CSmartPtr<T>& operator=(const CSmartPtr<T>&src)
    {
        if(this==&src)
            return *this;

        if(0==mpRefCnt->delRef()){
            delete mptr;
        }

        mptr = src.mptr;
        mpRefCnt = src.mpRefCnt;
        mpRefCnt->addRef();
        return *this;
    }

private:
    T *mptr;          // 指向资源的指针
    RefCnt<T> *mpRefCnt; // 指向该资源引用计数对象的指针
};

int main(){
    CSmartPtr<int> ptr1(new int);
    CSmartPtr<int> ptr2(ptr1);
    CSmartPtr<int> ptr3;
    ptr3 = ptr2;

    *ptr1 = 20;
    cout << *ptr2 << " " << *ptr3 << endl;

    return 0;
}

在这里插入图片描述

自定义删除器

#include <iostream>
#include <memory>
using namespace std;

template<class T>
class MyDeletor
{
public:
    void operator()(T* ptr)const
    {
        cout << "call MyDeletor.operator()" << endl;
        delete[] ptr;
    }
};

template<class T>
class MyFileDeletor
{
public:
    void operator()(T *ptr) const
    {
        cout << "call MyFileDeletor.operator()" << endl;
        fclose(ptr);
    }
};

int main(){
    unique_ptr<int, MyDeletor<int>> ptr1(new int[100]);
    unique_ptr<FILE,MyFileDeletor<FILE>> ptr2(fopen("data.txt", "w"));

    return 0;
}

在这里插入图片描述

像这样的删除器无疑是比较繁琐的,数组类型删除需要定义一个模板类,文件指针类型删除需要定义一个类。

所以我们可以通过c++11的新特性lambda表达式=》函数对象 function来简化代码

#include <iostream>
#include <memory>
#include<functional>
#include <stdio.h>
using namespace std;

int main(){
    unique_ptr<int,function<void(int*)>>ptr1(new int[100],
        [](int *p)->void{
            cout<<"call lambda release new int[100]"<<endl;
            delete[] p;
        }
    );

    unique_ptr<FILE,function<void(FILE*)>>ptr2(fopen("data.txt","w"),
        [](FILE *p)->void{
            cout<<"call lambda release new fopen"<<endl;
            fclose(p);
        }
    );

    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力学习的小马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值