POCO C++库学习和分析 -- 内存管理 (二)
POCO C++库学习和分析 -- 内存管理 (二)
3. SharedPtr
SharedPtr是Poco库中基于引用计数实现的另外一种智能指针。同AutoPtr相比,Poco::SharedPtr主要用于为没有实现引用计数功能的类(换句话说,也就是该类本身不是引用计数对象)提供引用计数服务,实现动态地址的自动回收。
可以这么说,Poco::AutoPtr是使用继承关系来实现的智能指针,而Poco::SharedPtr是聚合方法实现的智能指针。
3.1 SharedPtr的类图
首先来看一下SharedPtr的类图:
从类图中可以看到SharedPtr是对引用计数和原生指针封装。其中有成员指针_ptr,指向任意类型的C;同时还存在一个引用计数对象的指针_pCounter,指向任意一个实现了引用计数的类。当然在Poco库中提供了ReferenceCount的默认实现,类ReferenceCounter。
比较类ReferenceCounter和AutoPtr中依赖的类RefCountedObject,可以发现其实现相同,本质上就是一个东西。Poco库中之所以把两者分开,我想是为了明确的表示类与类之间的关系。ReferenceCounter用于组合,而RefCountedObject用于继承。
SharedPtr在实现模板的时候,还预留了RP参数,这是一个释放策略,用于调整SharedPtr在释放数组和单个对象之间不同策略的转换。
template <class C, class RC = ReferenceCounter, class RP = ReleasePolicy<C> > class SharedPtr { // ... }其中C为对象原生指针,RC为SharedPtr管理的引用计数对象,RP为内存释放策略。
3.2 SharedPtr操作符和值语义
1. Poco::SharedPtr同样支持关系操作符==, !=, <, <=, >, >=;
2. 当Poco::SharedPtr中原生指针为空时,使用解引用操作符“*”或者"->",Poco::SharedPtr会抛出一个NullPointerException 异常。
3. Poco::SharedPtr同样支持全值语义,包括默认构造函数,拷贝构造函数,赋值函数并且同样可以用于各类容器(如std::vector 和 std::map)
SharedPtr& operator = (C* ptr) { return assign(ptr); } SharedPtr& assign(C* ptr) { if (get() != ptr) { RC* pTmp = new RC; release(); _pCounter = pTmp; _ptr = ptr; } return *this; } void release() { poco_assert_dbg (_pCounter); int i = _pCounter->release(); if (i == 0) { RP::release(_ptr); _ptr = 0; delete _pCounter; _pCounter = 0; } }注意,在SharedPtr赋值操作符"="中的操作,对于原生指针_ptr的操作策略是交换,而引用计数对象_pCounter的策略是先new一个,再交换。
4. 可以用SharedPtr::isNull()和SharedPtr::operator ! () 去检查内部的原生指针是否为空。
3.3 SharedPtr和Cast类型转换
同普通指针类似,Poco::SharedPtr支持cast操作符。这在 template <class Other>SharedPtr<Other> cast() const中实现,其定义如下:template <class Other> SharedPtr<Other, RC, RP> cast() const /// Casts the SharedPtr via a dynamic cast to the given type. /// Returns an SharedPtr containing NULL if the cast fails. /// Example: (assume class Sub: public Super) /// SharedPtr<Super> super(new Sub()); /// SharedPtr<Sub> sub = super.cast<Sub>(); /// poco_assert (sub.get()); { Other* pOther = dynamic_cast<Other*>(_ptr); if (pOther) return SharedPtr<Other, RC, RP>(_pCounter, pOther); return SharedPtr<Other, RC, RP>(); }Poco::SharedPtr中类型转换总是安全的,在其内部实现时,使用了dynamic_cast ,所以一个不合法的转换,会导致原生指针为空。
Poco::SharedPtr中赋值操作符的兼容性通过构造函数和赋值操作符共同完成。
template <class Other, class OtherRP> SharedPtr& operator = (const SharedPtr<Other, RC, OtherRP>& ptr) { return assign<Other>(ptr); } template <class Other, class OtherRP> SharedPtr& assign(const SharedPtr<Other, RC, OtherRP>& ptr) { if (ptr.get() != _ptr) { SharedPtr tmp(ptr); swap(tmp); } return *this; } template <class Other, class OtherRP> SharedPtr(const SharedPtr<Other, RC, OtherRP>& ptr): _pCounter(ptr._pCounter), _ptr(const_cast<Other*>(ptr.get())) { _pCounter->duplicate(); }
下面是关于操作符的一个例子:
#include "Poco/SharedPtr.h" class A { public: virtual ~A() {} }; class B: public A { }; class C: public A { }; int main(int argc, char** argv) { Poco::SharedPtr<A> pA; Poco::SharedPtr<B> pB(new B); pA = pB; // okay, pB is a subclass of pA pA = new B; // pB = pA; // will not compile pB = pA.cast<B>(); // okay Poco::SharedPtr<C> pC(new C); pB = pC.cast<B>(); // pB is null return 0; }
3.4 SharedPtr使用注意事项
从上面我们可以看到Poco::SharedPtr拥有Poco::AutoPtr类似的一些特征,如解引用,赋值操作符。但同Poco::AutoPtr不同的是,当使用赋值操作符“=”把一个SharedPtr赋给一个原生指针,然后再把这个原生指针赋予另个SharedPtr时是不允许的。这时候两个SharedPtr都会声称拥有对象的所有权,将导致程序crash。在AutoPtr中虽然不推荐如此做,但提供了一个解决方案,使用以下函数,并至"shared=true"。
AutoPtr::AutoPtr(C* pObject, bool shared); AutoPtr& AutoPtr::assign(C* pObject, bool shared);
对于Poco::SharedPtr来说,最好的方法是一旦用SharedPtr获取到对象所有权后,就不要再试图使用指向对象的原生指针。
下面是SharedPtr的一个例子:
#include "Poco/SharedPtr.h" #include <string> #include <iostream> using Poco::SharedPtr; int main(int argc, char** argv) { std::string* pString = new std::string("hello, world!"); Poco::SharedPtr<std::string> p1(pString); // rc == 1 Poco::SharedPtr<std::string> p2(p1); // rc == 2 p2 = 0; // rc == 1 // p2 = pString; // BAD BAD BAD: multiple owners -> multiple delete p2 = p1; // rc == 2 std::string::size_type len = p1->length(); // dereferencing with -> std::cout << *p1 << std::endl; // dereferencing with * return 0; } // rc == 0 -> deleted
3.5 SharedPtr和数组
默认的SharedPtr删除策略是指删除对象。如果创建对象时使用数组,并把它委托给SharedPtr,必须使用对应数组删除策略。这时候SharedPtr的模板参数中ReleasePolicy应该使用类ReleaseArrayPolicy。
下面是对应的另一个例子:
template <class C> class ArrayReleasePolicy { public: static void release(C* pObj) /// Delete the object. /// Note that pObj can be 0. { delete [] pObj; } }; char* pStr = new char[100]; SharedPtr<char, Poco::ReferenceCounter, ArrayReleasePolicy> p(pStr);
3.6 其他
同boost库比较的话,Poco中的SharedPtr同boost库中的shared_ptr可以说是类似的,行为上相似,虽然实现不同。
(版权所有,转载时请注明作者和出处 http://blog.csdn.net/arau_sh/article/details/8636559)