c++智能指针的简单实现

为了管理具有指针成员的类,必须定义三个拷贝控制成员函数:拷贝构造函数、operato=、析构函数。这些成员可以定义指针成员的指针型子行为或之行为。
智能指针是行为类似于指针的的类对象,智能指针在对象间共享同一个基础值,从而提供了指针型行为。智能指针不需要去关注它的new和delete;他会在创建时自动new,并且在出作用域之前调用其析构函数将其delete,不会造成忘记delete或者无法执行delete而导致内存泄漏。
智能指针应该具有普通指针所具有的操作,所以在实现的时候应该对*和->进行重载。c++提供了auto_ptr、unique_ptr、shared_ptr。接下来一一的简单实现这几种智能指针。
一、auto_ptr(c++98提供的方法,c++11已经将其摒弃)
旧版的实现方式:
```

template
class AutoPtr{
public:
AutoPtr(T* ptr)
:_ptr(ptr), _use(true)
{}
AutoPtr(AutoPtr& ap)
:_ptr(ap._ptr), _use(ap._use)
{
ap._use = false;
}
~AutoPtr(){
if (_use == true)
delete _ptr;
}
T& operator*(){
return *_ptr;
}
T* operator->(){
return _ptr;
}
public: //为了方便说明,将原本应该是protected/private的成员变量设置为
T* _ptr;
bool _use;
};

   新版实现方式 

template
class AutoPtr{
public:
AutoPtr(T* ap)
:_ptr(ap)
{}
AutoPtr(AutoPtr& ap)
:_ptr(ap._ptr)
{
ap._ptr = NULL;
}
~AutoPtr(){
if (_ptr){
delete _ptr;
_ptr = NULL;
}
}
AutoPtr& operator=(AutoPtr& ap){
if (this != &ap){
delete _ptr;
_ptr = NULL;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
T& operator*(){
return *_ptr;
}
T* operator->(){
return _ptr;
}
protected:
T* _ptr;

};

比较这两种实现方式,均采用了转移使用权的做法,旧版写法是采用设置标志位来判断当前的对象是否合法,而新版写法则摒弃了标志位这个做法,而是将旧指针指控之后,将新对象的_ptr成员指向了变量。这样的做法有什么好处呢。请看下面的场景:
void DoSomthing()
{}
void Test(){
    AutoPtr<int> ap1(new int(1));
    DoSomthing();
    {
    AutoPtr<int> ap2(ap1);
    }   
    *ap1._ptr = 1;  
}

旧版运行以上测试程序的结果
旧版运行以上测试程序的结果:在ap1交出管理权给ap2之后,ap2已经释放掉了之前的空间,ap1仍然可以修改该空间。

新版运行以上测试程序的结果
新版运行以上测试程序的结果:在ap1交出管理权给ap2之后,将ap1._ptr置空,使其失去和ap2._ptr的关联。运行这段测试代码将会导致程序直接崩溃。
为了使auto_ptr具有和普通指针具有相同的基本功能(*&->),所以对这两个操作符进行operator。
二、unique_ptr(ScopedPtr)直接上实现代码

template <class T>
class ScopedPtr{
public:
    ScopedPtr(T* ptr = (new T))
    :_ptr(ptr){}
    ~ScopedPtr(){
        if (_ptr){
            delete _ptr;
            _ptr = NULL;
        }
    }
    T& operator*(){
        return *_ptr;
    }
    T* operator->(){
        return _ptr;
    }
protected:                       //防拷贝:只声明,不实现。
    ScopedPtr(ScopedPtr<T>& sp);
    ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
protected:
    T* _ptr;
};
这种智能指针为了解决多个对象同事指向同一块空间,直接采用了简单粗暴的禁止行为,不许进行拷贝构造和赋值操作符,从而避免了同一块空间被释放多次的问题。当不需要修改指针指向的情景下,unique_ptr将是最佳选择。

三、sheard_ptr

template <class T>
class SharePtr{
public:
    SharePtr(T* ptr = (new T))
        :_ptr(ptr), _count(new int(1))
    {}
    SharePtr(SharePtr<T>& sp)
        :_ptr(sp._ptr), _count(sp._count){
        ++(*_count);
    }
    SharePtr<T>& operator=(const SharePtr<T>& sp){
        if(_ptr != sp._ptr){
            if(--(*_count) == 0){
                delete _ptr;
                _ptr = NULL;
            }
            _ptr = sp._ptr;
            _count = sp._count;
            (*_count)++;
        }
        return *this;
    }
    SharePtr<T>& operator=(SharePtr<T> sp){
        swap(_ptr, sp._ptr);
        swap(_count, sp._count);
        return *this;
    }
    ~SharePtr(){
        if (--(*_count) == 0){
            delete _ptr;
            _ptr = NULL;
            delete _count;
            *_count = 0;
        }
    }
    T& operator*(){
        return *_ptr;
    }
    T* operator->(){
        return _ptr;
    }
protected:
    T* _ptr;
    int* _count;
};
这种智能指针为了解决同一块空间被多个对象同时指向的问题,采用了一种技术--引用计数(reference count),即sheard_ptr类将一个计数器与类指向的对象相关联。使用计数器跟踪该类有多少个对象共享同一指针。当计数器为零时,删除该对象。
每次构建新对象时,初始化指针并将_count = 1。当调用拷贝构造函数时,拷贝指针并且增加与之对应的计数器的值。对一个对象赋值时,赋值操作符减少做操作数所指对象的计数器(当计数器为0时,删除该对象),并增加右操作数所指对象的计数器的值。最后调用析构函数时,先将计数器减一,如果为0,则删除基础对象。
下面详细讨论operator=()这个操作的实现,下面第一种是传统的一种写法:
这个重载的问题有三个:a、自己给自己赋值问题;
                  b、两个指向同一块空间的对象的赋值;
                  c、正常情况。
    SharePtr<T>& operator=(const SharePtr<T>& sp){
        if(_ptr != sp._ptr){   //解决问题a、b
            if(--(*_count) == 0){
                delete _ptr;
                _ptr = NULL;
            }
            _ptr = sp._ptr;
            _count = sp._count;
            (*_count)++;
        }
        return *this;
    }
这种做法解决了这个问题,但是代码写起来比较繁琐,一个不小心,忘记了考虑一些特殊情况,就可能写出完善的解决办法。下面给出一种简洁的做法
    SharePtr<T>& operator=(SharePtr<T> sp){
        swap(_ptr, sp._ptr);
        swap(_count, sp._count);
        return *this;
    }
虽然仅有四行代码,但完全解决了这个问题。该方法充分利用了拷贝构造函数和析构函数。在调用operator=时,先调用拷贝构造函数使右操作数所指对象的引用计数器加一,然后交换左右操作数所指对象的指针和引用计数的值。在程序结束末尾再次调用析构函数将临时变量析构(即原先左操作数所值对象)。
三种智能指针的简单实现已经介绍完毕。还有一种智能指针没有介绍,那就是weak_ptr。这个指针是为了解决shared_ptr的循环引用而产生的。weak_ptr不会引起shared_ptr的引用计数增加,从而打破循环引用的尴尬。
struct Node{
    shared_ptr<int>* _next;
    shared_ptr<int>* _prev;
};
 void Test(){
    shared_ptr<Node>* n1;
    shared_ptr<Node>* n2;
    n1->_next = n2;
    n2->_prev = n1;
}

这段测试代的执行后,内存指向如下图:
循环引用内存模型
这个将导致n1和n2这两个对象的引用计数部位零,不能将其析构。下面这段代码代替之前的struct Node将有效地解决这个问题。

struct Node{
    weake_ptr<int>* _next;
    weake_ptr<int>* _Prev; 
}
只用weake_ptr智能指针,使得引用计数不加一,打破了这种循环引用。使得析构函数可以正确的释放掉对象。

总结:
1、c++11已经摒弃了auto_ptr这个类型,但仍然没有将其删除,这是为了支持一些以前编写的使用了auto_ptr的代码仍然可以正常使用。在我们以后编写代码时不要使用该指针。
2、当不需要拷贝构造和赋值操作时,最好使用unique_ptr。
3、在需要拷贝构造和赋值操作时,最好使用shared_ptr。
4、在与要解决shared_ptr的循环引用时,应使用weak_ptr。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值