智能指针——auto_ptr & scoped_ptr

智能指针的设计思想
为什么会出现智能指针?
首先我们先看一个可能会引发异常安全的例子:

#include <iostream>
using namespace std;

int div(int num1,int num2)
{
    if(num2 == 0)
    {
        throw string("除0错误");
    }
    return num1/num2;
}

void catch_div(int num1,int num2)
{
    try
    {
        div(num1,num2);
    }
    catch(const string& s)
    {
        cout<<s<<endl;
    }
}

int main()
{
    int* p = new int();
    catch_div(10,0);         // 在调用函数catch_div中有捕获异常,则程序直接跳到捕获异常的地方
    delete p;                // 不能正确完成delete指针,可能造成内存泄漏,引发异常安全的问题
    return 0;
}

在main()函数运行时可以看出,若在调用catch_div() 函数时存在捕获异常,则程序会跳到捕获的地方去,从而不会调用到后面的delete p, 可能就会造成内存泄漏的问题。
那如何解决呢?两种思路:(1)异常的重新抛出 (2)采用智能指针
采用智能指针的方式我们可以理解为,就像普通指针一样,但是它可以在调用的时候自动进行资源申请,调用结束后自动释放。

智能指针的设计思想:
(1)RAII思想:资源获得即初始化;
不同于普通类型指针,定义一个类对象指针,在构造的时候申请资源,在析构的时候释放资源,并且该类一定是一个模板类以适应不同类型的指针。
(2)像指针一样使用;
重载operator* operator-> 使这个类可以向指针一样来使用。

auto_ptr ( 管理权转移 )
(1)儿童时期的auto_ptr
在auto_ptr还是小孩的时候,他觉得自己只需要实现作为一个智能指针的普通功能就好(乖乖听妈妈的话),
在构造函数的时候申请资源,析构函数的时候释放资源,并且重载operator* operator-> 使他能够向指针一样使用。
但是随着年龄的增长他发现,当进行拷贝构造和赋值操作的时候进行的是浅拷贝,也就是多个智能指针管理的是同一块空间,在调用结束释放空间的时候也就会对这块空间进行多次释放,这肯定是不可以的!

(2)少年时期的auto_ptr
小auto_ptr想了很久,终于想到了一个办法
在进行拷贝构造和赋值操作的时候,将原来这块空间的管理权从原指针 ap1 转移到拷贝构造的新指针 ap2 上;
在实际编写代码时,我们只需要在进行浅拷贝之后,将原指针ap1赋为空指针NULL。
但是还有一个问题,进行拷贝构造完成后 ap1 == NULL ,当我们再想访问 ap1 的时候会程序崩溃(因为解引用空指针了),所以当前的这种方式也不可以!!!

(3)成年后的auto_ptr
成年后的auto_ptr有了一定自己的思维,决定在类的成员变量中加一个布尔类型的_owner,用来判断该对象是否具有对这块空间的管理权;
所以在进行拷贝构造和赋值运算符重载的时候,在浅拷贝之后,将原指针ap1的_owner 赋给 现在的指针ap2 的_owner, 之后对原指针ap1的_owner赋为false,这样对这块空间的管理权就从ap1手中转移到了ap2手中。

虽然auto_ptr思考了很多解决方案,但是它本身还是存在很大缺陷的,所以一般不建议使用auto_ptr。

下面是对于auto_ptr的简单实现:

#include <iostream>
using namespace std;

template <class T>
class auto_ptr
{
public:
    auto_ptr(T* ptr)
        :_ptr(ptr)
        ,_owner(true)
    {
        cout<<"auto_ptr()"<<endl;
    }

    auto_ptr(auto_ptr& ap)
        :_ptr(ap._ptr)
        ,_owner(ap._owner)
    {
        // ap._ptr = NULL; // 将对这块空间的管理权转移给当前对象

        ap._owner = false;
    }

    auto_ptr<T>& operator=(auto_ptr<T>& ap)
    {
        if(this != &ap)
        {
            if(_owner && _ptr)
            {
                delete _ptr;
            }
            _ptr = ap._ptr;
            // ap._ptr = NULL;

            _owner = ap._owner;
            ap._owner = false;
        }
        return *this;
    }

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

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

    ~auto_ptr()
    {
        if(_owner && _ptr)
        {
            delete _ptr;
            _owner = false;
        }
        cout<<"~auto_ptr()"<<endl;
    }

private:
    T* _ptr;
    bool _owner;
};

typedef struct
{
    int a;
    double c;
}A;


int main()
{
    /////////////////////////////////////////////////////////////////////////////
    // 1 查看能否实现智能指针的功能:
    // 在初始化时即开辟空间,析构时即释放空间
    // 能够向一般指针一样进行* -> 操作

    // auto_ptr<int> ap1(new int);
    // cout<<*ap1<<endl;
    // *ap1 = 10;
    // cout<<*ap1<<endl;


    // auto_ptr<A> ap1(new A);
    // ap1->a = 10;
    // ap1->c = 3.14;
    // cout<<(*ap1).a<<endl;
    // cout<<ap1->c<<endl;

    //////////////////////////////////////////////////////////////////////////////
    // 2 验证拷贝构造和 = 时会析构两次
    // 通过管理权转移能否解决该问题
    // auto_ptr<int> ap1(new int);
    // auto_ptr<int> ap2(ap1);
    // auto_ptr<int> ap3(new int);
    // ap3 = ap1;

    ////////////////////////////////////////////////////////////////////////////
    // 3 验证加入_owner 成员变量,
    // 解决访问已经将管理权转移的指针已经释放的问题
    auto_ptr<int> ap1(new int);
    *ap1 = 10;
    auto_ptr<int> ap2(ap1);
    auto_ptr<int> ap3(new int);
    ap3 = ap1;
    cout<<*ap1<<endl;

    return 0;
}

简单粗暴的scoped_ptr(防拷贝)
scoped_ptr 见到 auto_ptr 在拷贝上存在那么大的问题,就非常简单粗暴的想:不是只有拷贝构造和赋值运算符重载函数在调用时会存在问题吗,那我不让别人调用这两个函数就好了。
实现防拷贝只需要两步
(1)只声明,不实现
(2)定义为私有

下面是对于scoped_ptr的简单实现:

#include <iostream>
using namespace std;

template <class T>
class scoped_ptr
{
public:
    scoped_ptr(T* ptr)
        :_ptr(ptr)
    {
        cout<<"scoped_ptr()"<<endl;
    }

    // 只声明,不实现,就不会出现别人拷贝构造和赋值的情况
    // 但是,有可能别人在类外面进行定义!!!
    // scoped_ptr(scoped_ptr<T>& sp);
    // scoped_ptr<T>& operator=(scoped_ptr<T>& sp);

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

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

    ~scoped_ptr()
    {
        if(_ptr)
        {
            delete _ptr;
        }
        cout<<"~scoped_ptr()"<<endl;
    }

    // 将拷贝构造和赋值定义为私有的,这样在类外面也不能访问它
    // 为什么不能只将这两个函数定义为私有,但是还实现它??
    // 因为也要防止有可能有人将访问的函数定义为友元friend,这样更保险一些
private:
    scoped_ptr(scoped_ptr<T>& sp);
    scoped_ptr<T>& operator=(scoped_ptr<T>& sp);

private:
    T* _ptr;
};

int main()
{
    scoped_ptr<int> sp1(new int);
    *sp1 = 10;
    cout<<*sp1<<endl;
    // scoped_ptr<int> sp2(sp1);
    // scoped_ptr<int> sp3(new int);
    // sp3 = sp1;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_39294633/article/details/81750628