AutoPtr 实现

智能指针产生的原因

由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。但是 new 完之后做了一些事情,到最后旧经常会忘记delete。例如在一个类构造函数 new 在析构的时候才会释放。程序员在写 析构的时候不一定还记得 delete。
因此在 Poco c++ 中提供了一种引用计数机制实现的智能指针模板类。仔细阅读下面你就就可以自己实现一个 c++ 引用计数方式的智能指针。

poco::AutoPtr 使用的例子

以下为使用的例子。其中 xxx 为某个 class。
Poco::AutoPtr ptr = new xxx;
Poco::AutoPtr ptr2( new xxx);
Poco::AutoPtr ptr3 = ptr2;
在以上的代码中我们有两次 new 但是都不需要 delete.因为 AutoPtr 会在没有任何 AutoPtr 指针指向它的时候帮我们析构掉。
如果仅仅是简单的析构时候 delete 明显是会出错的。例如上面的例子中。ptr2 ,ptr3 是指向同一块内存的。如果 ptr3 在使用 而ptr2 析构的时候释放,那么会造成很严重的后果可能是崩溃。
这说明了什么?
说明了 Poco::AutoPtr 的智能指针不是简单的在析构的时候 delete 的。
应该是在最后一个指向 xxx 对象的 AutoPtr 消亡时候才 delete掉。也就是说需要一个引用计数机制,来记录了有几个智能指针指向 xxx 类型的对象。

下面介绍两个概念

引用计数:
1.无论何时一个引用被销毁或者重写,它所引用的对象的引用计数都会减少。
2.无论何时一个引用被创建或者被拷贝,它的引用的对象的引用计数都会增加。
3.初始时的引用计数是 1。
4.当一个对象的引用为 0 时,这个对象资源被销毁。
5.在多线程情况下增加和减少必须是原子操作。

对象拥有权:
1.任何对象都可能拥有一个或多个所有者。
2.只要一个对象至少还拥有一个所有者,它就会继续存在。
3.如果一个对象没有所有者,那么可以被销毁。

Poco::AutoPtr 的实现就是基于一个引用计数的思想。
因为需要 AutoPtr 指向的类拥有引用计数的一个功能。为了方便起见。我们先实现一个基类。后面的类如果要用到智能指针的,只要继承此引用计数类即可获得计数功能。
下面 是RefCountedObj 类代码。来源于 poco 只是为了让代码简洁,把计数器 _counter 类型换成 uint32_t。poco 里是一个 AutomicCounter 类支持多线程的原子 ++ ,–。这里为了理清整体实现,先不考虑多线程。

/**
 * Copyright (c) 2015, xxx(版权声明)
 *
 * @file    RefContedObj.h      
 * @brief 一个引用计数类
 *
 * 提供了 duplicate() 来增加引用计数。release() 来减少引用计数。
 * 每次 release() 时候判断引用是否为 0 如果为0 那么 delete 资源。
 * 析构函数为 protected 不允许 外部 delete 或者栈中使用。
 *
 * @author:         yuhaiyang
 * @date:       2015年4月24日
 *
 * 修订说明:初始版本
 */
#ifndef REFCOUNTEDOBJECT_H_
#define REFCOUNTEDOBJECT_H_

#include <stdint.h>

class RefCountedObject
{
public:
    RefCountedObject();
        /// Creates the RefCountedObject.
        /// The initial reference count is one.

    void duplicate() const;
        /// Increments the object's reference count.

    void release() const throw();
        /// Decrements the object's reference count
        /// and deletes the object if the count
        /// reaches zero.
        ///< 用来释放 this 的方法。


    int referenceCount() const;
        /// Returns the reference count.

protected:
    /// Destroys the RefCountedObject.
    ///< 此处声明为 protected 的意义是在栈内存不能使用。只能通过new 使用。
    ///< 不能使用 delete 释放,只有本类的 release() 或者派生类 release 能够delete。
    virtual ~RefCountedObject()
    {
    }

private:
    RefCountedObject(const RefCountedObject&);
    RefCountedObject& operator = (const RefCountedObject&);

    ///< 引用计数, 在多线程注意互斥。
    mutable uint32_t _counter;
};


//
// inlines
//
inline RefCountedObject::RefCountedObject()
:_counter(1)
{
}


inline int RefCountedObject::referenceCount() const
{
    return _counter;
}


inline void RefCountedObject::duplicate() const
{
    ++_counter;
}


inline void RefCountedObject::release() const throw()
{
    try
    {
        if (--_counter == 0) delete this;
    }
    catch (...)
    {

    }
}



#endif /* REFCOUNTEDOBJECT_H_ */

这个类提供了 Poco::AutoPtr 需要的 duplicate(), release(),这两个方法。
duplicate() 每次调用 _counter 都会加1 。release() 方法每次调用 _counter 都会减 1,并且如果 _counter是0 的话,就会调用 delete this ;就可以析构自己。
每次有新的只能指针指向这个对象的时候只要调用此对象duplicate()就可以了。
这里将类的析构声明为protected 的作用是不允许能自动析构的。只能调用 release() 先检测 _counter 是否为0 再来确定是否delete ,所以是安全的。 Poco::AutoPtr 每次析构也是调用 release()方法由对象自己的引用计数来判断是否释放。

有了引用计数类,也清楚了该怎么使用了,下面我们要开始分析 Poco::AutoPtr。
Poco::AutoPtr.
AutoPtr模板类图

从上图可以看出来,想要使用智能指针的类,不需要每次自己实现一个引用计数类,都只要继承于 RefCountedObject 类就可以了。

AutoPtr构造和赋值:

1.当 AutoPtr 从 T* ,拷贝构造或者赋值的时候 AutoPtr 获取对象的拥有权。引用计数不变。
1)当从一个 T* 构造AutoPtr,AutoPtr拥有 T 的拥有权,引用计数不变。
2)当赋值一个 T* 到AutoPtr,AutoPtr拥有T的拥有权,引用计数不变。

2.当 AutoPtr 从 AutoPtr 构造或者赋值的时候这两个 AutoPtr 共同拥有对象的拥有权,引用计数增加。
1)当从另外一个AutoPtr构造AutoPtr,这两个AutoPtr共享T的拥有权,引用计数增加。
2)当赋值另一个AutoPtr到AutoPtr,这两个AutoPtr共享T的拥有权,引用计数增加。
上面1.1 要求我们重载AutoPtr 的从 T* 的构造函数

    /**
     * @brief :从一个 T* 构造一个 AutoPtr对象。
     * @parm [in] 从 T* 的原生指针构造一个 AutoPtr 对象。
     */
    AutoPtr(  T* ptr ):_ptr( ptr )
    {
    }

1.2 要求我们重载从 T* 的 operator= 函数

    /**
     *@brief 使用 T* 来赋值给 AutoPtr .
     */
    AutoPtr& operator= ( T* other )
    {
        return assign( other );
    }

2.1 要求重载从 AutoPtr 对象构造一个AutoPtr 对象。注意这里有两个只能指针指向对象,所以要调用计数类的 duplicate()

    /**
     * @brief 从一个 AutoPtr对象 构造一个新的 AutoPtr 对象。
     * @parm: [in] ptr 从
     */
    AutoPtr( const AutoPtr& ptr ):_ptr( ptr._ptr )
    {
        if( _ptr )
        {
            //引用计数 ++
            _ptr->duplicate();
        }
    }

2.2 要求重载从一个 AutoPtr 赋值到另一个 AutoPtr 类的。需要注意的是左值之前指向一个对象的话那么要先 release()。

    /**
     *@brief 使用 T* 来赋值给 AutoPtr .
     */
    AutoPtr& operator= ( T* other )
    {
        return assign( other );
    }

    AutoPtr& assign( T* other )
    {
        ///< 判断是否是本身。
        if( _ptr  != other )
        {
            ///< 释放之前的内存。
            if( _ptr )
            {
                _ptr->release();
            }
            _ptr = other;
        }
        return *this;
    }

AutoPtr 支持的操作和语义:

1.Poco::AutoPtr支持相关的操作:==,!=,<,<=,>,>=。

2.当进行解引用操作:*,->,如果指针为空,抛出NullpointerException异常。

3.Poco::AutoPtr支持完全的值操作相关语义:默认构造函数,拷贝构造函数,赋值,它也可以被用在集合:std::vector等中。

4.使用AutoPtr::isNull()或者AutoPtr::operator !()测试是否指针为空。

下面是实现部分,只有基本功能,但是对于想搞清楚原理足够了。

/**
 * Copyright (c) 2015,xxx(版权声明)
 *
 * @file    AutoPtr.h       
 * @brief AutoPtr 的简单实现。
 *
 * 一个基于引用计数的只能指针,会自动释放资源。
 *
 * @author:         yuhaiyang
 * @date:       2015年4月24日
 *
 * 修订说明:初始版本
 */
#ifndef AUTOPTR_H_
#define AUTOPTR_H_

#include <stdio.h>

namespace My
{
class NullPointer : public std::exception
{
public:
    NullPointer()
    {
    }
    virtual const char* what( void ) const throw()
    {
        return "this Pointer is NULL";
    }
    ///< 析构函数必须声明为不 抛出异常。
    ~NullPointer() throw()
    {
    }
};


/*
 * @brief 智能指针模板类。
 * 
 * 使用引用计数机制的智能指针。
 * 想要使用引用计数机制的类必须是 RefCountedObject 类的派生类。
 * 因为需要提供 duplicate() 方法,和 release() 方法。
 */
template <class T>
class AutoPtr
{
public:
    /**
     * @brief 默认的构造函数没有实际的值。
     */
    AutoPtr( ):_ptr( NULL )
    {
    }
    /**
     * @brief :从一个 T* 构造一个 AutoPtr对象。
     * @parm [in] 从 T* 的原生指针构造一个 AutoPtr 对象。
     */
    AutoPtr(  T* ptr ):_ptr( ptr )
    {
    }

    /**
     * @brief 从一个 AutoPtr对象 构造一个新的 AutoPtr 对象。
     * @parm: [in] ptr 从
     */
    AutoPtr( const AutoPtr& ptr ):_ptr( ptr._ptr )
    {
        if( _ptr )
        {
            //引用计数 ++
            _ptr->duplicate();
        }
    }
    /**
     * @brief 析构的时候调用 _ptr->release() 计数减减。
     */
    virtual ~AutoPtr( )
    {
        if( _ptr )
        {
            _ptr->release();
        }
    }

    /**
     * @brief 获取 ptr 对象。
     */
    T* get( void )
    {
        return _ptr;
    }

    / operator 重载 ///

    /**
     * @brief 取值操作
     */
    T& operator*() const
    {
        if( _ptr )
        {
            return *_ptr;
        }else
        {
            throw NullPointer();
        }
    }
    /**
     * @brief -> 的重载
     */
    T* operator ->()
    {
        if( _ptr )
        {
            return _ptr;
        }else
        {
            throw NullPointer();
        }
    }

    /**
     *@brief 使用 T* 来赋值给 AutoPtr .
     */
    AutoPtr& operator= ( T* other )
    {
        return assign( other );
    }

    AutoPtr& assign( T* other )
    {
        ///< 判断是否是本身。
        if( _ptr  != other )
        {
            ///< 释放之前的内存。
            if( _ptr )
            {
                _ptr->release();
            }
            _ptr = other;
        }
        return *this;
    }

    /**
     * @brief 使用 AutoPtr 来赋值 AutoPtr。
     */
    AutoPtr& operator=( AutoPtr &other )
    {
        return ( assign( other ) );
    }

    AutoPtr& assign( AutoPtr& other )
    {
        ///< 判断是不是本身
        if( this != &other )
        {
            if( _ptr )
            {
                _ptr->release();
            }
            ///< 把当前类的 _ptr 指向 other._ptr 。
            _ptr = other._ptr;
            if( _ptr )
            {
                //引用计数增加。
                _ptr->duplicate();
            }
        }
        return *this;
    }

    /**
     * @brief 关系运算符 ==
     */
    bool operator == ( const AutoPtr& other ) const
    {
        return ( _ptr == other._ptr );
    }
    bool operator == ( T* ptr ) const
    {
        return ( _ptr == ptr );
    }
    /**
     * @brief 关系运算符 !=
     */
    bool operator != ( const AutoPtr& other) const
    {
        return ( _ptr != other._ptr );
    }
    bool operator != ( const T* ptr) const
    {
        return ( _ptr != ptr );
    }

    /**
     * @brief 关系运算符小于,
     *
     * @warning 注意 AutoPtr 进行排序是按照地址进行的。这里设计不是很合理。
     * 应该调用用户的 class T 重载的 operator< 而不单纯是地址比较。
     */
    bool operator < ( const AutoPtr& other ) const
    {
        return ( _ptr < other._ptr );
    }

    bool operator < ( const T* ptr ) const
    {
        return ( _ptr < ptr );
    }

    /**
     * @brief 关系运算符大于
     */
    bool operator >( const AutoPtr& other ) const
    {
        return ( _ptr > other._ptr );
    }

    bool operator >( const T* ptr ) const
    {
        return ( _ptr > ptr );
    }

    /**
     * @brief 强制类型转换操作符 (type)xxx。
     */
    operator T* ()
    {
        return _ptr;
    }
    /**
     * @brief 判断是否为空 。_ptr 是 NULL 返回 true,否则返回 false。
     */
    bool operator ! () const
    {
        return _ptr == 0;
    }

    bool isNull() const
    {
        return _ptr == 0;
    }

private:
    T *_ptr;
};







} /* namespace My */

#endif /* AUTOPTR_H_ */

总结:
1.最好不要 先使用原始指针 new 然后赋值给 AutoPtr 指针。
例如:
C* p = new C;
AutoPtr ptr = p;
因为 p 是一个风险它不遵循 AutoPtr 的规矩。
更好的做法
AutoPtr ptr (new C);
2.智能指针的比较是指向的地址的值比较。
3.如果这个类只允许 new 出来的,那么析构声明为 protected。
4.由于 AutoPtr 指针是一个对象,所以 if( AutoPtr ) 一直都是真。判断指针内容是否为空使用 if( !ptr ) 这个取反方法的重载,或者使用 if( ptr.isNull ) 这个方法来判断 。

Poco 交流群:386220502

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值