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
- {
- // ...
- }
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;
- }
- }
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中赋值操作符的兼容性通过构造函数和赋值操作符共同完成。
- 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可以说是类似的,行为上相似,虽然实现不同。