2013年08月27日

C++ 构造智能指针


尝试自己构造一个智能指针

在C++语言中,内存泄漏的问题很让人头疼,程序猿需要时刻自己管理好内存分配与回收,稍有意外就会忘记释放某个资源还更有可能是由于各种异常导致资源释放语句来不及执行。对于这个问题,C++中提高了auto_ptr和tr1.share_ptr来类智能指针来处理。

在Effective C++中介绍,如果要管理资源的问题,那么就要遵循这样一个原则,以对象管理资源,这句话的意思是我们需要使用的资源保存在一个对象中,对象自动完成资源的析构,这样程序猿就从头疼的资源释放中解放出来了。当然,对象管理资源,背后还有一个知识点,那就是局部对象总是会在函数结束时被析构,不论函数如何结束,正常的方式,还是触发异常的方式。

auto_ptr和tr1.share_ptr正是以对象管理资源的观点来实现了所谓的智能指针。让智能指针持有对象,自动完成对象的析构,资源的释放。这样的用法,在Effective C++和More Effective C++中强调了很多次。尤其是赋值构造函数,构造函数,析构函数中或者其他可能导致异常的操作中,这样的情况,大多数可以使用智能指针灵活的处理。

auto_ptr

auto_ptr是C++提供的智能指针之一,使用方式如下:

std::auto_ptr<Class> autoptr(new Class()); 

就个语句就表达了以智能指针管理资源的两个关键想法:

1.获得资源后立即放进管理对象,如上面new了新对象,则立即存放到智能指针中

2.管理对象运用析构函数确保资源释放,如上面过了autoptr的作用域,则调用析构函数delete class

然而,auto_ptr智能指针还存在一些问题.

1.auto_ptr被销毁时会自动删除它所包含的对象,这样就不能让多个auto_ptr指向同一个对象 2.正也由于上面的原因,所以auto_ptr的赋值和复制构造函数被改造了,原来的对象置空,也正是这个原因,STL容器不能存储auto_ptr,STL容器需要能有正常赋值和复制构造函数的对象。 3.auto_ptr不能支持数组对象,因为在析构中调用的是delete关键字

shared_ptr

auto_ptr存在的这个问题就有了更新的解决方案,tr1:shared_ptr指针,引用计数智能指针,对于数组的的问题则可以使用vector代替。

实现

基于上面的一些思想,我们可以自己实现一个简单的智能指针对象,还重载指针访问符等等。

#include<iostream>

using namespace std;

template<typename T>
class SmallPtr{

public:
    SmallPtr(T* t);

    SmallPtr(SmallPtr& smallptr);
    SmallPtr<T>& operator=(SmallPtr& smallptr);

    T* operator->();

    ~SmallPtr();

private:
    T* ptr;
    int *refcount;

    void clear();
};

template<typename T>
SmallPtr<T>::SmallPtr(T* t):ptr(t){
    refcount = new int(1);
}

template<typename T>
SmallPtr<T>::SmallPtr(SmallPtr& smallptr){
    ptr = smallptr.ptr; 
    refcount = smallptr.refcount;
    ++*refcount;
}

template<typename T>
SmallPtr<T>& SmallPtr<T>::operator=(SmallPtr& smallptr){
    if(ptr != smallptr.ptr){ 
        if(--*refcount == 0){
            clear();
        }
        ptr = smallptr.ptr; 
        refcount = smallptr.refcount; 
        ++*refcount;
    }
    return *this;
}

template<typename T>
T* SmallPtr<T>::operator->(){
    return ptr;
}

template<typename T>
void SmallPtr<T>::clear(){
    if(ptr != NULL){
        delete ptr;
        ptr = NULL;
    }
    if(refcount != NULL){
        delete refcount;
        refcount = NULL;
    }
}

template<typename T>
SmallPtr<T>::~SmallPtr(){
    if(--*refcount == 0)
        clear();
}

class TestPtr{
public:
    TestPtr(){
        cout << "construct" << endl;
    }

    void print(){
        cout << "I am a print function" << endl;
    }

    ~TestPtr(){
        cout << "descontruct" << endl;
    }

};

int main(){
    SmallPtr<TestPtr> small(new TestPtr());   
    small->print();
    SmallPtr<TestPtr> small2 = small;
    return 0;
}

总结

  1. 在上面的简单实现中,需要考虑对象的引用技术,在赋值构造函数中,需要多加注意只能指针是否引用的是同一个对象,就类似于普通的赋值构造函数中,可能需要考虑是否是相同的对象在彼此赋值。
  2. 除了重载赋值和复制,其实还需要重载->和* 操作符,这样对对象的使用,依然还是之前的方式。
  3. 以对象来管理资源的概念,值得好好思考。
前一篇: C++ 迭代器的简单实现 后一篇: Openstack Git汇总