用对象来管理资源,以防出现资源泄漏等问题。对于堆上对象这种资源,一种用来管理它们的比较好的对象就是智能指针。
使用智能指针可以大大方便堆上对象的管理,特别是释放。对于堆上分配的对象,需要我们手动的释放,因而释放的时机选择总是一个比较棘手的问题。释放早了的话,可能那个对象还在被使用中,于是就是一堆的野指针,最后出现了的SIGSEGV,或者莫名其妙的死锁,还搞不清楚到底是哪里出了问题。释放得晚,或者遗漏,那就是memory leak,最后造成稳定性问题,也是很不容易追查真凶到底在什么地方。而栈上的对象,其分配与释放都由编译器帮我们完美的自动处理,使用起来就方便多了。智能指针的应用,使得对堆上分配对象的管理几乎可以变得和栈上对象的管理一样方便。使用智能指针还能方便数据的共享。由于实现了非常丰富的操作符,及良好的复制行为。在与标准库里的容器一起使用时也非常方便,表现良好。
当然智能指针也有不好的地方,或者说存在风险的地方。如果两个用智能指针管理的对象,他们相互之间通过智能指针引用到对方,那么将难免会造成memory leak。要避免这种问题,那估计得要再实现一些复杂的算法来检测这种环状引用了就像JVM为Java开发者所做的那样。智能指针的使用也还是需要多加小心。
在这里我们就来看一下智能指针的使用和实现。C++中的智能指针,就复制行为而言,可以分为2大类。一是如C++标准库里的auto_ptr这样,复制行为实际上是该智能指针所管理对象管理权的转移。二是如C++标准库里的shared_ptr这样,基于引用计数来实现,在复制时仅仅是增加引用计数。一个智能指针对象销毁时,减少其管理的对象的引用计数,在引用计数被减小至0时,对象被释放。基于引用计数实现的指针,依据其引用计数机构的不同,又分为两类:一是引用计数机构在被智能指针管理的对象中,如Poco库里的AutoPtr;二是有一个独立的引用计数机构来记录对象被引用的次数,如标准库里的shared_ptr。
此处将分析几种智能指针的使用与实现:STL的std::auto_ptr及std::tr1::shared_ptr,Poco库的AutoPtr,及android framework中常见到的sp。STL的std::auto_ptr及std::tr1::shared_ptr基于android 5.1 codebase的代码来分析,这两个模板类的code分别位于external/stlport/stlport/stl/_auto_ptr.h和prebuilts/ndk/current/sources/cxx-stl/gnu-libstdc++/include/tr1/shared_ptr.h。android的sp,其定义在system/core/include/utils/StrongPointer.h。
在创建std::auto_ptr对象的时候,传递要由它管理的指针给它,在此对象析够的时候,它所管理的指针会自动的被delete。
先来看一下用法:
class TestObj: public RefCountedObject { public: TestObj(); virtual ~TestObj(); }; TestObj::TestObj() { } TestObj::~TestObj() { } typedef Poco::AutoPtr<TestObj> TestObjPtr; typedef list<TestObjPtr> TestObjList; void test_ap() { Poco::AutoPtr<TestObj> ptr = new TestObj; cout << "Initial refcount = " << ptr->referenceCount() << endl; Poco::AutoPtr<TestObj> ptr2 = ptr; cout << "Copy one time refcount = " << ptr->referenceCount() << endl; list<Poco::AutoPtr<TestObj> > testObjList; testObjList.push_back(ptr); cout << "Push into a list refcount = " << ptr->referenceCount() << endl; testObjList.remove(ptr); cout << "Remove from a list refcount = " << ptr->referenceCount() << endl; ptr2 = new TestObj; cout << "refcount = " << ptr->referenceCount() << endl; TestObjPtr ptr3; ptr3 = ptr2; cout << "ptr2 refcount = " << ptr2->referenceCount() << endl; ptr3 = new TestObj; cout << "ptr2 refcount = " << ptr2->referenceCount() << endl; ptr3 = ptr2; cout << "ptr2 refcount = " << ptr2->referenceCount() << endl; }
如下是上面这段code执行的结果:
Initial refcount = 1 Copy one time refcount = 2 Push into a list refcount = 3 Remove from a list refcount = 2 refcount = 1 ptr2 refcount = 2 ptr2 refcount = 1 ptr2 refcount = 2
可以看到对象的引用计数随着流程的执行而改变,AutoPtr对象每复制一次,其管理的对象的引用计数就加一,而AutoPtr对象每释放一次,其管理的对象的引用计数就减一。
接着来具体说明一下要如何使用AutoPtr。首先,由智能指针来管理其对象释放的class需要继承Poco库的RefCountedObject。AutoPtr是一种基于引用计数的堆对象管理机制,因而,总要有个地方来存放一个对象的引用次数。AutoPtr管理的对象,用于记录引用计数的变量存放在该对象中,但那个变量继承自RefCountedObject(RefCountedObject提供的当然不止是这样的一个变量)。
一定要继承RefCountedObject才能使用Poco的AutoPtr吗?答案是不一定。但要由AutoPtr管理其对象的类,一定要符合模板AutoPtr所要求的隐式接口。具体点说,就是要提供RefCountedObject提供的接口,就像网上有些地方看到的例子,让那些类自己定义那些函数。当然接口的行为也要与RefCountedObject基本一致,比如,增加引用计数,减少引用计数,在引用计数减小为0时释放对象,否则的话,AutoPtr的作用也不能真正展现。
然后就可以RAII(资源获取即初始化),使用AutoPtr来管理new的对象了。我们可以放心的像使用Java里面的对象一样使用AutoPtr,而不用担心会有memory leak。
接着我们来看Poco::AutoPtr这套机制的实现。主要就是class RefCountedObject和模板AutoPtr的定义。先来看RefCountedObject的定义与实现:
#include "Poco/Foundation.h" #include "Poco/AtomicCounter.h" namespace Poco { class Foundation_API RefCountedObject /// A base class for objects that employ /// reference counting based garbage collection. /// /// Reference-counted objects inhibit construction /// by copying and assignment. { 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. int referenceCount() const; /// Returns the reference count. protected: virtual ~RefCountedObject(); /// Destroys the RefCountedObject. private: RefCountedObject(const RefCountedObject&); RefCountedObject& operator =(const RefCountedObject&); mutable AtomicCounter _counter; }; // // inlines // inline int RefCountedObject::referenceCount() const { return _counter.value(); } inline void RefCountedObject::duplicate() const { ++_counter; } inline void RefCountedObject::release() const throw () { try { if (--_counter == 0) delete this; } catch (...) { poco_unexpected(); } } } // namespace Poco下面是这个class的实现文件:
RefCountedObject::RefCountedObject(): _counter(1) { } RefCountedObject::~RefCountedObject() { }
这个类只有一个原子类型AtomicCounter的成员变量_counter,用来记录对象被引用的次数。可以看到它是被声明为mutable的,也就是说,即使是对于const的对象,在const方法中,也可以修改这个变量的值。
这个类提供了三个操作duplicate()、release()和referenceCount(),用来修改或读取_counter的值。这三个操作全都被声明为const,配合声明为mutable的_counter,使得AutoPtr这套机制,即使是对于new的const class也一样可用。duplicate()操作中,会增加引用计数,release()操作中会减少引用计数,referenceCount()操作则会将对象当前被引用的次数返回给调用者。在release()操作中,如果引用技术减到了0,当前对象会被delete掉。这些操作究竟会在什么地方调用到呢?答案是AutoPtr,后面看到模板AutoPtr的定义就能明白了。
这个class还采用了一些方法来阻止对于它的继承体系中对象的复制行为。可以看到,它声明了private的copy构造函数和赋值操作符,但两个函数都没有被定义。private声明可以在编译期阻止那些对于这个class的private成员没有访问权限的部分复制对象,而有意的不定义这两个函数,则可以在链接期阻止对这个class的private成员有访问权限的部分,比如一些friend class,friend方法,或内部的一些函数复制对象。
接着来看AutoPtr模板的定义:
template<class C> class AutoPtr /// AutoPtr is a "smart" pointer for classes implementing /// reference counting based garbage collection. /// To be usable with the AutoPtr template, a class must /// implement the following behaviour: /// A class must maintain a reference count. /// The constructors of the object initialize the reference /// count to one. /// The class must implement a public duplicate() method: /// void duplicate(); /// that increments the reference count by one. /// The class must implement a public release() method: /// void release() /// that decrements the reference count by one, and, /// if the reference count reaches zero, deletes the /// object. /// /// AutoPtr works in the following way: /// If an AutoPtr is assigned an ordinary pointer to /// an object (via the constructor or the assignment operator), /// it takes ownership of the object and the object's reference /// count remains unchanged. /// If the AutoPtr is assigned another AutoPtr, the /// object's reference count is incremented by one by /// calling duplicate() on its object. /// The destructor of AutoPtr calls release() on its /// object. /// AutoPtr supports dereferencing with both the -> /// and the * operator. An attempt to dereference a null /// AutoPtr results in a NullPointerException being thrown. /// AutoPtr also implements all relational operators. /// Note that AutoPtr allows casting of its encapsulated data types. { public: AutoPtr() : _ptr(0) { } AutoPtr(C* ptr) : _ptr(ptr) { } AutoPtr(C* ptr, bool shared) : _ptr(ptr) { if (shared && _ptr) _ptr->duplicate(); } AutoPtr(const AutoPtr& ptr) : _ptr(ptr._ptr) { if (_ptr) _ptr->duplicate(); } template<class Other> AutoPtr(const AutoPtr<Other>& ptr) : _ptr(const_cast<Other*>(ptr.get())) { if (_ptr) _ptr->duplicate(); } ~AutoPtr() { if (_ptr) _ptr->release(); } AutoPtr& assign(C* ptr) { if (_ptr != ptr) { if (_ptr) _ptr->release(); _ptr = ptr; } return *this; } AutoPtr& assign(C* ptr, bool shared) { if (_ptr != ptr) { if (_ptr) _ptr->release(); _ptr = ptr; if (shared && _ptr) _ptr->duplicate(); } return *this; } AutoPtr& assign(const AutoPtr& ptr) { if (&ptr != this) { if (_ptr) _ptr->release(); _ptr = ptr._ptr; if (_ptr) _ptr->duplicate(); } return *this; } template<class Other> AutoPtr& assign(const AutoPtr<Other>& ptr) { if (ptr.get() != _ptr) { if (_ptr) _ptr->release(); _ptr = const_cast<Other*>(ptr.get()); if (_ptr) _ptr->duplicate(); } return *this; } AutoPtr& operator =(C* ptr) { return assign(ptr); } AutoPtr& operator =(const AutoPtr& ptr) { return assign(ptr); } template<class Other> AutoPtr& operator =(const AutoPtr<Other>& ptr) { return assign<Other>(ptr); } void swap(AutoPtr& ptr) { std::swap(_ptr, ptr._ptr); } template<class Other> AutoPtr<Other> cast() const /// Casts the AutoPtr via a dynamic cast to the given type. /// Returns an AutoPtr containing NULL if the cast fails. /// Example: (assume class Sub: public Super) /// AutoPtr<Super> super(new Sub()); /// AutoPtr<Sub> sub = super.cast<Sub>(); /// poco_assert (sub.get()); { Other* pOther = dynamic_cast<Other*>(_ptr); return AutoPtr<Other>(pOther, true); } template<class Other> AutoPtr<Other> unsafeCast() const /// Casts the AutoPtr via a static cast to the given type. /// Example: (assume class Sub: public Super) /// AutoPtr<Super> super(new Sub()); /// AutoPtr<Sub> sub = super.unsafeCast<Sub>(); /// poco_assert (sub.get()); { Other* pOther = static_cast<Other*>(_ptr); return AutoPtr<Other>(pOther, true); } C* operator ->() { if (_ptr) return _ptr; else throw NullPointerException(); } const C* operator ->() const { if (_ptr) return _ptr; else throw NullPointerException(); } C& operator *() { if (_ptr) return *_ptr; else throw NullPointerException(); } const C& operator *() const { if (_ptr) return *_ptr; else throw NullPointerException(); } C* get() { return _ptr; } const C* get() const { return _ptr; } operator C*() { return _ptr; } operator const C*() const { return _ptr; } bool operator !() const { return _ptr == 0; } bool isNull() const { return _ptr == 0; } C* duplicate() { if (_ptr) _ptr->duplicate(); return _ptr; } bool operator ==(const AutoPtr& ptr) const { return _ptr == ptr._ptr; } bool operator ==(const C* ptr) const { return _ptr == ptr; } bool operator ==(C* ptr) const { return _ptr == ptr; } bool operator !=(const AutoPtr& ptr) const { return _ptr != ptr._ptr; } bool operator !=(const C* ptr) const { return _ptr != ptr; } bool operator !=(C* ptr) const { return _ptr != ptr; } bool operator <(const AutoPtr& ptr) const { return _ptr < ptr._ptr; } bool operator <(const C* ptr) const { return _ptr < ptr; } bool operator <(C* ptr) const { return _ptr < ptr; } bool operator <=(const AutoPtr& ptr) const { return _ptr <= ptr._ptr; } bool operator <=(const C* ptr) const { return _ptr <= ptr; } bool operator <=(C* ptr) const { return _ptr <= ptr; } bool operator >(const AutoPtr& ptr) const { return _ptr > ptr._ptr; } bool operator >(const C* ptr) const { return _ptr > ptr; } bool operator >(C* ptr) const { return _ptr > ptr; } bool operator >=(const AutoPtr& ptr) const { return _ptr >= ptr._ptr; } bool operator >=(const C* ptr) const { return _ptr >= ptr; } bool operator >=(C* ptr) const { return _ptr >= ptr; } private: C* _ptr; }; template<class C> inline void swap(AutoPtr<C>& p1, AutoPtr<C>& p2) { p1.swap(p2); }
这个模板类,主要实现了一些创建、复制、销毁动作及其它的一些操作符。创建、复制(copy构造函数及赋值操作符)及销毁动作是智能指针行为的核心之所在,创建、复制时,需要正确地增加对象的引用计数,而在销毁时,则要减少只能指针。
既然称之为智能指针,那自然就不能少了常规的指针都有的一些操作方式,比如解引用或通过箭头操作符访问对象成员,因而,不出意料的这个模板类也重载了operator ->和operator *这两个操作符。它还提供了类型智能指针到对象类型指针的转换操作operator C*/operator const C*,及获取对象指针的操作。
除此之外,它还提供了一系列基于指针值的比较操作。
AutoPtr接收单个指针作为参数的构造函数,使得编译器可以实施由一个对象指针到智能指针的隐式类型转换。所造成的问题就是,一不小心,编译器自己创建了一个AutoPtr,但对象的引用计数没有增加,后续智能指针对象在销毁的时候,它所追踪的对象的引用计数会提前减小到0,然后对象会被提前释放。比如传递this指针,调用一个接受该对象的智能指针为参数的函数。但其实这种情况下,是应该要调用那个需要一个额外的bool型参数的构造函数来创建智能指针对象,以使得对象的引用计数能被适当的增加的。由使用者角度来看,Poco的AutoPtr的这种设计增加了用户使用它的风险,因而将AutoPtr的AutoPtr(C* ptr)构造函数声明为explicit以挡掉编译器的隐式类型转换似乎要更好,更安全一点。
android sp的用法
std::tr1::shared_ptr的用法及实现