C++智能指针系列:其一、auto_ptr

一、内存泄露

C++没有内存管理机制,对于建立在堆上的内存需要手动释放。比如我们new了一个对象,会占用堆上的一块内存,返回指针,如果不手动delete这个指针,那么这块内存就会一直被占用,直到进程结束后被操作系统回收,这就是所谓的内存泄露。

class Test {
public:
    Test() { cout << "Test start..." << endl; }
    ~Test() { cout << "Test end..." << endl; }
    int getDebug() { return this->debug; }
private:
    int debug = 20;
};

int main ( )
{
    Test * test = new Test();
}

结果仅仅输出了:
Test start…
说明没有调用析构函数,这块内存一直被这个已经不需要的对象占用着。

二、智能指针的基本思想

首先很容易想到一个事情:对于建立在栈上的对象,我们不必考虑对象本身内存泄露的问题。因为当函数结束后,编译器会自动对栈上的对象调用析构函数。所以,我们可以把堆上对象的指针,托管给栈上的对象。当栈上对象调用析构函数的时候,连带delete堆上对象的指针。
于是,我们可以很好的总结智能指针的基本要素。
(1)智能指针本质上是一个模板类,模板参数表示实际上要托管的指针指向的对象的类型。
(2)这个模板类有一个成员变量,类型是:指向模板参数类型的指针,由这个成员变量存储被托管的指针。
(3)这个模板类的构造函数应该将模板参数类型的指针作为参数,以便于创建对象时候传入被托管指针。
(4)这个模板类的析构函数中,应该delete被托管指针。
(5)这个模板类应该自定义拷贝构造函数,实现被托管指针控制权的转移。但是注意,在通常的拷贝构造函数中,参数类型为const引用,但是这里应该是非const的引用,因为要保证被拷贝的智能指针,解除对于堆对象指针的托管。
(6)这个模板类应该有自定义的赋值构造函数,原理同上。
(7)通过这个模板类的对象,可以拿到被托管的指针。
(8)既然名字叫智能指针,那么这个模板类一定要用起来像指针才对。所以它要重载->运算符,重载*运算符。
上述逻辑可以说是足够简单了,这就是大名鼎鼎的智能指针的最初设计思想。在2003年版本的C++中将其付诸实践,这就是第一代智能指针:auto_ptr。

三、auto_ptr

基本思想在上面了,我们大可以自己实现一个智能指针。本人没有看过03版本的auto_ptr源码,但是基本思想相信大差不差。
以下是自己实现的auto_ptr代码:

template<typename Ty>     //Ty是被托管的指针类型
class my_ptr{
private:
    Ty * ty;     //被托管的指针
public:
    explicit my_ptr(Ty * ty1=0):ty(ty1){}

    ~my_ptr(){delete ty;}    //析构函数中释放资源

    Ty * get(){          //返回被托管的指针
        return ty;
    }

    Ty * release(){       //转移被托管的指针
        Ty * temp = ty;
        ty = 0;
        return temp;
    }

    void reset(Ty * ty2){      //重置被托管的指针
        delete ty;
        ty = ty2;
    }

    Ty * operator ->(){       //重载->和*运算符,让智能指针用起来像指针
        return ty;
    }

    Ty & operator * (){
        return *(ty);
    }

    my_ptr(my_ptr & ty1){     //拷贝构造函数
        this->ty = ty1.release();
    }

    my_ptr& operator = (my_ptr && ty1 ){   //赋值运算符重载
        reset(ty1.release());
        return *this;
    }
};

我们可以测试使用一下:
(1)测试自动内存管理:

class Test {
public:
    Test() { cout << "Test start..." << endl; }
    ~Test() { cout << "Test end..." << endl; }
    int getDebug() { return this->debug; }
private:
    int debug = 20;
};

int main ( )
{
    my_ptr<Test> ptr1(new Test());
}

输出:

Test start...
Test end...

自动释放堆内存。
(2)测试运算符重载:

class Test {
public:
    Test() { cout << "Test start..." << endl; }
    ~Test() { cout << "Test end..." << endl; }
    int getDebug() { return this->debug; }
private:
    int debug = 20;
};

int main ( )
{
    my_ptr<Test> ptr1(new Test());
    cout<<ptr1->getDebug()<<endl;
    cout<<(*ptr1).getDebug()<<endl;
}

输出:

Test start...
20
20
Test end...

用起来像指针。

(3)测试拷贝构造函数与移动构造函数的资源转移:

class Test {
public:
    Test() { cout << "Test start..." << endl; }
    ~Test() { cout << "Test end..." << endl; }
    int getDebug() { return this->debug; }
private:
    int debug = 20;
};

int main ( )
{
    my_ptr<Test> ptr1(new Test());
    cout<<ptr1.get()<<endl;
    my_ptr<Test> ptr2(ptr1);
    cout<<ptr2.get()<<endl;
    cout<<ptr1.get()<<endl;
}

输出:

Test start...
0x23270d418e0
0x23270d418e0
0
Test end...

拷贝构造函数,资源转移成功。
赋值运算符重载也一样,不演示了。

四、auto_ptr的弊端

如果使用Clion等高级IDE,你会发现auto_ptr会被标记中间划线,系统提醒你,这个只能指针尽可能不要用。
这个设计看起来非常棒,但是实际上,03版的智能指针,是一个非常草率,甚至可以说是一个非常失败的设计。auto_ptr问世以后,不久就被程序员集体抛弃,以至于现在提智能指针,大家都认为是C++11中才提出来的。
这个设计究竟有什么致命缺陷呢?看代码:

class Test {
public:
    Test() { cout << "Test start..." << endl; }
    ~Test() { cout << "Test end..." << endl; }
    int getDebug() { return this->debug; }
private:
    int debug = 20;
};

void f(auto_ptr<Test> ptr){}

int main ( )
{
    auto_ptr<Test> ptr(new Test());
    f(ptr);
    cout<<ptr->getDebug();
}

其中f不过是一个空函数,也会导致程序崩溃退出,为什么呢?因为将ptr传过去以后,会在栈上建立临时对象,调用运算符重载函数对其赋值,相当于将ptr持有的指针转移给临时对象!这个时候ptr中托管的就是一个空指针,你在不知不觉的情况下还想用就会导致程序崩溃!
简单地说,就是auto_ptr不带限制的资源转移,可能会产生比内存泄漏更恶心的事情。这个问题直到C++11才解决,至于怎么解决的,敬请期待下一篇~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值