c++智能指针(1)

本文介绍了C++中的智能指针,包括std::auto_ptr、std::unique_ptr、std::shared_ptr和std::weak_ptr。详细阐述了它们的功能和使用场景,如std::auto_ptr的独占所有权和不适用于STL容器,std::unique_ptr作为std::auto_ptr的替代品支持数组管理,std::shared_ptr通过引用计数管理多个智能指针共享对象,以及std::weak_ptr解决循环引用问题。同时,讨论了QT框架中的QScopedPointer、QSharedPointer和QWeakPointer,与C++标准库智能指针的相似性和差异。

智能指针

智能指针是对指针变量进行管理,实现指针变量指向内存的自动回收。

c++标准中的智能指针

std::auto_ptr, c++03标准引入,提供简单的垃圾回收机制,不支持管理数组对象(new[]); 独占对象所有权,赋值和reset都会传递所有权,并且将右值auto_ptr管理对象设置为空,因此不支持与标准模板库容器一起使用。

std::uique_ptr, c++11标准引入,用来取代std::auto_ptr,独占对象所有权,能够支持数组兑现管理,并且支持标准模板库容器。

std::shared_ptr, c++11标准引入,内部使用引用计数管理分配和释放对象,允许多个shared_ptr共享相同对象,可以使用std::make_shared函数快速创建;因为使用了引用计数,所以要注意循环引用问题。

std::weak_ptr, c++11标准引入,是配合std::shared_ptr工作的智能指针,只能通过share_ptr或者weak_ptr来构造,可以说是share_ptr管理的指针对象的一个引用,因此不能够检测到引用计数变为0时指针对象的自删除,不能够直接获取对象,但是可以通过lock函数获取share_ptr对象,通过weak_ptr函数能够有效的解决share_ptr中可能遇到的循环引用问题

QT中的智能指针

QScopedPointer,作用域指针,与std::auto_ptr类似,独占对象所有权,但是通过私有拷贝构造函数和赋值函数,支持数组对象管理,不允许所有权转移来避免std::auto_ptr中可能出现的问题。

QSharedPointer, 与std::shared_ptr类似,也是通过智能指针管理对象分配和释放。

QWeakPointer, 与std::weak_ptr类似,配合QSharedPointer来使用。

源码之auto_ptr实现

    // auto_ptr_ref模板类主要是为了实现auto_ptr引用的语义的代理类,参考auto_ptr中的解释
    template<class _Ty>
    struct auto_ptr_ref
    {
        explicit auto_ptr_ref(_Ty *_Right): _Ref(_Right){}
        _Ty *_Ref;
    };

    template<class _Ty>
    class auto_ptr
    {
    public:
        typedef auto_ptr<_Ty> _Myt; // 定义auto_ptr<_Ty>类型别名为_Myt,减少输入
        typedef _Ty element_type;   // 定义_Ty类型别名为element_type,下面都没有用到,感觉微软也像copy过来的代码

        // 默认构造函数
        // explicit关键字限定不允许隐试类型转换限定如下情况不能编译
        // void foo(auto_ptr<_Ty>); foo(new _Ty);
        // 函数体后面添加throw表示不抛出任何异常,这时当函数体内有异常发生时,程序就会终止
        // throw(int)表示只抛出int类型异常,不添加throw,表示可以抛出任何异常
        explicit auto_ptr(_Ty *_Ptr = 0) throw()()
            : _Myptr(_Ptr)
        {}

        // 拷贝构造函数,调用release函数释放_Right对指针对象的所有权,实现所有权转移,
        // 从而保证所有权唯一, 注意release并没有回收指针对象指向内存
        auto_ptr(_Myt& _Right) throw()
            : _Myptr(_Right.release())
        {}

        // 类型转换操作符,将auto_ptr<_Ty>转换为auto_ptr<_Other>
        // 例如:
        // auto_ptr<int> an(new int(3));
        // auto_ptr<float> af = (auto_ptr<float>)an;
        template<class _Other>
        operator auto_ptr<_Other>() throw()
        {
            return (auto_ptr<_Other>(*this));
        }

        // 基于模板的赋值函数,调用release函数释放_Right对指针对象所有权
        // 调用reset释放本地存储的指针对象,获取_Right管理的指针对象
        // 暗含了基于模板的数据类型转换
        template<class _Other>
        _Myt& operator=(auto_ptr<_Other>& _Right) throw()
        {
            reset(_Right.release());
            return (*this);
        }

        //基于模板的拷贝构造函数,暗含了隐式数据类型转换
        template<class _Other>
        auto_ptr(auto_ptr<_Other>& _Right) throw()
            : _Myptr(_Right.release())
        {}

        // 赋值函数,获取_Right对象对管理的指针对象所有权,
        // 释放本地管理的指针对象内存
        _Myt& operator=(_Myt& _Right) throw()
        {   
            reset(_Right.release());
            return (*this);
        }

        // 析构函数,因为是使用delele的方式
        // 只能是管理new出来的指针对象,不能管理malloc对象和数据对象
        ~auto_ptr()
        {   // destroy the object
            delete _Myptr;
        }

        // 重载*运算符,可以通过*获取指针对象的值 
        // 如:int *p = new int(3); auto_ptr<int> a(p); assert(*p == *a);
        _Ty& operator*() const throw()
        {   
            return (*get());
        }

        // 重载->运算符,可以通过->获取指针对象
        // struct S { int m; int n }
        // S *p = new S; auto_ptr<S> a(p); assert(a->m == p->m);
        _Ty *operator->() const throw()
        {
            return (get());
        }

        // 获取指针对象
        _Ty *get() const throw()
        {
            return (_Myptr);
        }

        // 返回指针对象,并且将本地指针设置为0
        // 因此调用release并没有释放内存,仅仅是转移了对象所有权
        _Ty *release() throw()
        {   
            _Ty *_Tmp = _Myptr;
            _Myptr = 0;
            return (_Tmp);
        }

        // 回收指针对象内存,将本地指针指向新的指针对象
        void reset(_Ty *_Ptr = 0)
        {   
            if (_Ptr != _Myptr)
                delete _Myptr;
            _Myptr = _Ptr;
        }

        // 因为auto_ptr的拷贝构造函数和赋值函数要转移对象所有权(调用release)
        // 所以其拷贝构造函数和赋值构造函数的参数都为auto_ptr&,
        // 而不是const auto_ptr&, 所以当我们使用诸如auto_ptr<T> a(auto_ptr<T>(new T())时
        // 因为auto_ptr<T>(new T())临时对象为const,所以不能通过编译
        // 因此使用auto_ptr_ref来实现auto_ptr&的语义,使上面这种情况依然能够通过编译

        // 参数为auto_ptr_ref的拷贝构造,保证auto_ptr<T> a(auto_ptr<T>(new T())
        auto_ptr(auto_ptr_ref<_Ty> _Right) throw()
        {   
            _Ty *_Ptr = _Right._Ref;
            _Right._Ref = 0;    
            _Myptr = _Ptr;  
        }

        // 类型转换操作符,将auto_ptr<_Ty>转换为auto_ptr_ref<_Other>
        template<class _Other>
        operator auto_ptr_ref<_Other>() throw()
        {   
            // 这里如果_Ty与_Other类型不能进行隐式类型转换,编译器会报错
            _Other *_Cvtptr = _Myptr; 
            auto_ptr_ref<_Other> _Ans(_Cvtptr);
            _Myptr = 0; 
            return (_Ans);
        }

        // 赋值函数,实现auto_ptr<_Ty> a(NULL); a = auto_ptr<_Ty>(new _Ty());
        _Myt& operator=(auto_ptr_ref<_Ty> _Right) throw()
        {   
            _Ty *_Ptr = _Right._Ref;
            _Right._Ref = 0;    
            reset(_Ptr);    
            return (*this);
        }

        private:
        _Ty *_Myptr;    

    }; 

注意:

正是由于auto_ptr如上实现,有如下缺点:

  1. 不能用于STL container,因为STL container要求储存的元素能够”copy-constructible”和”assignable”,也就是能够安全的对元素进行拷贝和赋值而不影响原有值,而auto_ptr的拷贝和赋值函数都是要转移所有权
  2. 不能管理指针数组对象,因为析构函数使用delete来释放指针
  3. 因为VC编译器并没有要求右值的const,auto_ptr_ref在VC编译器下并没有什么卵用

源码之QScopedPointer实现

    // 回收T类型指针对象所指向内存(通过new T分配的)
    template <typename T>
    struct QScopedPointerDeleter
    {
        static inline void cleanup(T *pointer)
        {
            // 前向声明又叫不完全数据声明,这里主要是用来保证在编译期,前向声明已经定义
            // 例如: class A; class B { QScopedPointer<A> a; B(){};}
            // 编译期就会出现使用了为定义类型A和负下标错误
            typedef char IsIncompleteType[ sizeof(T) ? 1 : -1 ];
            (void) sizeof(IsIncompleteType);
            delete pointer;
        }
    };

    // 回收指向T类型数组的指针对象所指向内存(通过new T[]分配)
    template <typename T>
    struct QScopedPointerArrayDeleter
    {
        static inline void cleanup(T *pointer)
        {
            typedef char IsIncompleteType[ sizeof(T) ? 1 : -1 ];
            (void) sizeof(IsIncompleteType);

            delete [] pointer;
        }
    };

    // 回收通过malloc分配的内存
    struct QScopedPointerPodDeleter
    {
        static inline void cleanup(void *pointer) { if (pointer) free(pointer); }
    };

    // QT中在QEventLoop中使用的某些QObject对象,需要在QEventLoop某些事件发生后,才能够回收
    #ifndef QT_NO_QOBJECT
    template <typename T>
    struct QScopedPointerObjectDeleteLater
    {
    static inline void cleanup(T *pointer) { if (pointer) pointer->deleteLater(); }
    };

    class QObject;
    typedef QScopedPointerObjectDeleteLater<QObject> QScopedPointerDeleteLater;
    #endif

    // 作用域智能指针,模板参数为T和默认值为QScopedPointerDeleter<T>的Cleanup类型
    // Cleanup可以使用Qt提供的如上4个struct类型,也可以自定义自己的数据类型
    // 需要实现它static void cleanup(T* pointer)函数
    template <typename T, typename Cleanup = QScopedPointerDeleter<T> >
    class QScopedPointer
    {
        // T**类型 别名QScopedPointer::RestrictedBool
        typedef T *QScopedPointer:: *RestrictedBool;
        public:
        // 构造函数,explicit不允许隐式类型转换
        explicit inline QScopedPointer(T *p = 0) : d(p)
        {}

        // 析构函数,调用Cleanup::cleanup函数释放内存
        inline ~QScopedPointer()
        {
            T *oldD = this->d;
            Cleanup::cleanup(oldD);
        }

        // 重载operator*,使用智能指针与使用本地指针获取指向值一样
        // T* p = new T; QScopedPointer<T> s(p); Q_ASSERT(*s == *p)
        inline T &operator*() const
        {
            Q_ASSERT(d);
            return *d;
        }

        // 重载operator->,使使用智能指针与使用本地指针指向一致
        // struct A { int m, int n};
        // struct *p = new A; QScopedPointer<p> s(p); Q_ASSERT(s->m == p->m);
        inline T *operator->() const
        {
            Q_ASSERT(d);
            return d;
        }

        // 重载operator!
        // QScopedPointer<T> s; if (!s){};
        inline bool operator!() const
        {
            return !d;
        }

    #if defined(Q_QDOC)
        // 重载operator bool()类型转换符
        // QScopedPointer<T> s; if (s)具有意义
        inline operator bool() const
        {
            return isNull() ? 0 : &QScopedPointer::d;
        }
    #else
        // 重载operator RestrictedBool()类型转换符
        // QScopedPointer<T> s; if (s)具有意义
        inline operator RestrictedBool() const
        {
            return isNull() ? 0 : &QScopedPointer::d;
        }
    #endif

        // 获取指针对象
        inline T *data() const
        {
            return d;
        }

        // 判断指针对象是否为空,与operator!()一致
        inline bool isNull() const
        {
            return !d;
        }

        // 将指针对象设置为other,释放原有指针对象
        inline void reset(T *other = 0)
        {
            if (d == other)
                return;
            T *oldD = d;
            d = other;
            Cleanup::cleanup(oldD);
        }

        // 返回指针对象,并释放指针对象所有权
        inline T *take()
        {
            T *oldD = d;
            d = 0;
            return oldD;
        }

        // 交换指针对象
        inline void swap(QScopedPointer<T, Cleanup> &other)
        {
            qSwap(d, other.d);
        }

        typedef T *pointer;

    protected:
        T *d;

    private:
        // 将拷贝构造函数与赋值函数设置为私有,不允许通过拷贝构造函数和赋值函数获取指针对象
        Q_DISABLE_COPY(QScopedPointer)
    };

    // 重载operator==,判断两个智能指针是否管理相同的指针对象
    template <class T, class Cleanup>
    inline bool operator==(const QScopedPointer<T, Cleanup> &lhs, const QScopedPointer<T, Cleanup> &rhs)
    {
        return lhs.data() == rhs.data();
    }

    // 重载operator!=, 判断两个智能指针管理的指针对象是否不相等
    template <class T, class Cleanup>
    inline bool operator!=(const QScopedPointer<T, Cleanup> &lhs, const QScopedPointer<T, Cleanup> &rhs)
    {
    return lhs.data() != rhs.data();
    }

    namespace QtPrivate {
        // 声明一个模板参数为X,Y类型的结构
        template <typename X, typename Y> struct QScopedArrayEnsureSameType;
        // 部分特化(partial specification)上述声明,确保X,Y类型一致
        template <typename X> struct QScopedArrayEnsureSameType<X,X> { typedef X* Type; };
        // 部分特化(partial specification)上述声明,确保X,Y类型一致
        template <typename X> struct QScopedArrayEnsureSameType<const X, X> { typedef X* Type; };
    }

    // 模板参数为T,Cleanup类型并且Cleanup默认值为QScopedPointerArrayDeleter<T>,用来释放指向数组的指针
    // 继承QScopedPointer
    template <typename T, typename Cleanup = QScopedPointerArrayDeleter<T> >
    class QScopedArrayPointer : public QScopedPointer<T, Cleanup>
    {
    public:
        // 默认构造函数
        inline QScopedArrayPointer() : QScopedPointer<T, Cleanup>(0) {}

        // 参数类型为D的模板构造函数
        // 通过 typename QtPrivate::QScopedArrayEnsureSameType<T,D>::Type 参数确保D与T类型一致
        // 这里的typename不能用class替代,因为这里是嵌套数据类型
        // 这里还不太清除为什么不直接是有T*p作为参数
        template <typename D>
        explicit inline QScopedArrayPointer(D *p, typename QtPrivate::QScopedArrayEnsureSameType<T,D>::Type = 0)
            : QScopedPointer<T, Cleanup>(p)
        {
        }

        // 重载operator[],使使用智能指针与指向数组指针一致
        // T* p = new T[10]; QScopedArrayPointer<T> s(p); Q_ASSERT(s[0] == p[0])
        inline T &operator[](int i)
        {
            return this->d[i];
        }

        // 重载operator[],使使用智能指针与指向数组指针一致
        inline const T &operator[](int i) const
        {
            return this->d[i];
        }

    private:
        // 确保模板数据类型与指针对象数据类型一致
        explicit inline QScopedArrayPointer(void *) {
        }
        // 私有拷贝构造函数和赋值函数
        Q_DISABLE_COPY(QScopedArrayPointer)
    };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值