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

posted @ 2013-03-05 14:45  在天与地之间  阅读(890)  评论(0编辑  收藏  举报