POCO中的内存管理
概要:引用计数;自动指针,引用计数对象和自动释放池;共享指针;动态工厂和实例化;内存池;单例
一、引用计数
引用计数是对例如对象或者内存块这一类资源通过记录资源的引用、指针或者句柄的次数
进行有效管理的一种技巧。它最典型的作用就是释放不再使用的对象资源。
当一个引用被销毁或重置,它所引用对象的引用计数就要相应减少。
当一个引用被创建或拷贝,它所引用对象的引用计数就要相应增加。
一个对象的引用初始值是1。
当对象的引用计数到零的时候,这个对象就被删除。
在多线程的场景下,增加、减少或者比较引用计数必须是自动的不可拆分的原子操作。
2、对象的所有权
谁掌握对象的“所有权”,谁就负责在对象不再需要的情况下把对象删除。如果“所有者”
删除失败,就会导致内存泄露。
其他使用者可以使用对象,但绝不可以删除这个对象。
所有权是可以转移的,但同一时间只能有一个所有者。
3、引用计数和所有权
拥有对象所有权的指针不会增加引用计数,这意味着对象之前没有所有者,换句话说就是
这个对象刚刚被创建出来,或者之前的所有者放弃了所有权(当然这样也不会增加对象的引用计数)。
通常情况下,对象创建之后的第一个指针拥有所有权。
自动指针类模板
Poco::AutoPtr是实现了引用计数的“智能”指针,Poco::AutoPtr可以被任何支持引用计数的类实例化。
一个类要支持引用计数就必须:1、保持一个引用计数(创建时初始化为1)2、实现void duplicate()方法
增加引用计数 3、实现void release()方法减少引用计数,并在计数到零时删除对象。
自动指针的创建和分配
当从C*创建一个自动指针AutoPtr<C>时,该自动指针拥有C的所有权(但引用计数保持不变)。
当把AutoPtr<C>赋值给一个C*指针时,自动指针拥有C的所有权(但引用计数保持不变)。
当从一个AutoPtr<C>创建另外一个AutoPtr<C>时,两个自动指针共享C的所有权,引用计数加1。
当把一个AutoPtr<C>赋值给另一个AutoPtr<C>时,两个自动指针共享C的所有权,引用计数加1。
程序示例:
引用计数对象
Poco::RefCountedObject实现了线程安全的引用计数,截至1.3.4在可能的情况下
它使用了平台相关的原子操作。
Poco::RefCountedObject可以作为实现引用计数类的基类。
Poco::RefCountedObject的销毁方法是被保护的(protected),禁止了拷贝构造和赋值。
所有引用计数对象的销毁都应该是被保护的(protected),这样是为了防止显式的使用delete删除。
程序示例:
自动指针的操作和语义
Poco::AutoPtr支持关系操作符:==, !=, <, <=, >, >=
如果指针为空,解析操作符: *, -> 将抛出NullPointerException异常。
Poco::AutoPtr支持缺省构造、拷贝构造、赋值操作,可以被用于collections集合
(比如std::vector 或者 std::map)
使用AutoPtr::isNull() 或者 AutoPtr::operator ! ()测试是否为空。
自动指针和转换
像普通指针一样,Poco::AutoPtr支持转换操作。
template <class Other> AutoPtr<Other> cast() const
AutoPtr的转换是类型安全的(在内部用的是dynamic_cast,所以非法转换会返回空指针)
支持通过模板构造和赋值操作给兼容的自动指针赋值。
程序示例:
自动指针附加说明和注意点
把AutoPtr赋值给普通指针,然后又把这个普通指针赋值给其他AutoPtr要特别小心!
两个AutoPtr都有对象的所有权,这是不好的设计。
需要共享所有权的情况要明确声明。比如
AutoPtr::AutoPtr(C* pObject, bool shared);
AutoPtr& AutoPtr::assign(C* pObject, bool shared);
shared必须为true!
示例:
自动释放池
Poco::AutoReleasePool类模版关注单一应用对象。
#include "Poco/AutoReleasePool.h"
Poco::AutoReleasePool对所有加入自己的对象有所有权。
当Poco::AutoReleasePool被销毁(或release方法被调用),它调用每个对象的release()方法,
释放所有对象的引用。
共享指针类模板
Poco::SharedPtr对自己没有实现引用计数的类的引用计数功能。
#include "Poco/SharedPtr.h"
Poco::SharedPtr与Poco::AutoPtr有相同的特性(解析、关系操作等)
注意:将指向同一对象的普通指针赋值给不同的Poco::SharedPtr会导致对象有多个所有者,
引起不可预料的行为(也就是崩溃)
一旦对一个对象使用了Poco::SharedPtr,就不要再用普通指针指向该对象。
共享指针操作和语义
Poco::SharedPtr支持比较操作:==, !=, <, <=, >, >=,解析操作: *, ->
如果指针为空,抛出NullPointerException异常。
Poco::SharedPtr支持缺省构造、拷贝构造、赋值构造,可以被用在
std::vector和 std::map这样的集合中。
用SharedPtr::isNull() 或者 SharedPtr::operator ! () 测试是否为空
Poco::SharedPtr支持cast转换,template <class Other> SharedPtr<Other> cast() const
SharedPtr的cast转换是类型安全的,支持通过模板构造和赋值构造给兼容的SharedPtr赋值。
共享指针和数组
共享指针的默认实现是简单的调用delete pObj,但这对通过new[]创建的对象来说是错误的,
应该用delete[] pObj来实现。
要用自定义的释放策略创建共享指针,用以下代码:
动态工厂类模板
Poco::DynamicFactory支持通过名字创建对象。
#include "Poco/DynamicFactory.h"
DynamicFactory管理的所有类必须有一个共同的基类。DynamicFactory从这个基类进行实例化。
C* DynamicFactory::createInstance(const std::string& name) const;
从给定名字创建C的子类的实例。
要做到这些,类和他们的实例器(工厂类)必须在DynamicFactory中注册。
实例器(Instantiator)类模板
C类实例器通常一定是Poco::AbstractInstantiator<Base>的子类,其中Base是C的基类。
Poco::Instantiator是工厂类的模板。它定义了基本方法createinstance(),这个方法
使用new操作创建类的新实例。
考虑到类继承,一个实例器(Instantiator)总是从定义了createInstance()方法
的Poco::AbstractInstantiator继承。
动态工厂和实例器
为了Poco::Instantiator类模板能工作,类必须是可默认构造的。
如果一个类不是可默认构造的,或者它需要特殊的构造步骤,你就必须自己实现实例器类。
缓冲区(Buffer)管理
当面对已有的C库调用或者系统级调用,经常需要提供特定大小的缓冲区。
如果缓冲区比较大,它就需要在堆(heap)中分配内存。这时候需要对内存进行适当的管理,
保证当不再使用甚至发生异常的时候缓冲区能够被释放。
std::auto_ptr 或者 Poco::SharedPtr (带有默认的释放策略)无法在这里应用,
因为他们不能应用于数组。
缓冲区类模板
Poco::Buffer被用来提供在堆上分配的固定大小的缓冲区,在缓冲对象作用域之外会自动删除缓冲对象。
#include "Poco/Buffer.h"
begin()方法返回指向缓冲区首部的指针,end()方法返回指向尾部的指针。
index操作符提供对单个元素的访问功能。
内存池
很多程序都频繁的分配和释放给定大小的缓冲区。在堆内存上分配缓冲有性能上的限制,
并且经常导致堆内存碎片化。因此,已分配缓冲的重复使用很有意义。
Poco::MemoryPool正是完成这项功能。
内存池类
Poco::MemoryPool类容纳固定大小的内存块集合。
#include "Poco/MemoryPool.h"
void* MemoryPool::get()调用传递出一个内存连续区的指针。如果已经没有可用内存,
就新分配一块内存。内存的总大小是有限制的,如果超过最大限制,就抛出OutOfMemoryException异常。
void MemoryPool::release(void* ptr)将内存块释放回内存池中。
单例
单例设计模式用来限制某个类只能实例化一个对象。这对于需要一个全局对象协调整个系统内各操作时是非常必要的。
有时,只有一个对象实例或者对象实例越少,操作越有效率。单例模式也经常被用来作为全局变量的一种委婉的实现
方式。单例模式不可滥用,在把一个类设计为单例之前,要认真考虑设计一个普通类并仅使用一个对象是否就足够。
如果完全肯定必须使用单例,POCO提供了Poco::SingletonHolder类来帮组实现线程安全的“懒汉式单例”管理。
#include "Poco/SingletonHolder.h"
单例对象实例当第一次被请求时在堆内存上创建,当应用结束时被销毁。
单例模式和双重检查锁定机制,记住一定不要使用双重检查锁定机制实现单例模式的线程安全访问。
使用示例
概要:引用计数;自动指针,引用计数对象和自动释放池;共享指针;动态工厂和实例化;内存池;单例
一、引用计数
引用计数是对例如对象或者内存块这一类资源通过记录资源的引用、指针或者句柄的次数
进行有效管理的一种技巧。它最典型的作用就是释放不再使用的对象资源。
当一个引用被销毁或重置,它所引用对象的引用计数就要相应减少。
当一个引用被创建或拷贝,它所引用对象的引用计数就要相应增加。
一个对象的引用初始值是1。
当对象的引用计数到零的时候,这个对象就被删除。
在多线程的场景下,增加、减少或者比较引用计数必须是自动的不可拆分的原子操作。
2、对象的所有权
谁掌握对象的“所有权”,谁就负责在对象不再需要的情况下把对象删除。如果“所有者”
删除失败,就会导致内存泄露。
其他使用者可以使用对象,但绝不可以删除这个对象。
所有权是可以转移的,但同一时间只能有一个所有者。
3、引用计数和所有权
拥有对象所有权的指针不会增加引用计数,这意味着对象之前没有所有者,换句话说就是
这个对象刚刚被创建出来,或者之前的所有者放弃了所有权(当然这样也不会增加对象的引用计数)。
通常情况下,对象创建之后的第一个指针拥有所有权。
自动指针类模板
Poco::AutoPtr是实现了引用计数的“智能”指针,Poco::AutoPtr可以被任何支持引用计数的类实例化。
一个类要支持引用计数就必须:1、保持一个引用计数(创建时初始化为1)2、实现void duplicate()方法
增加引用计数 3、实现void release()方法减少引用计数,并在计数到零时删除对象。
自动指针的创建和分配
当从C*创建一个自动指针AutoPtr<C>时,该自动指针拥有C的所有权(但引用计数保持不变)。
当把AutoPtr<C>赋值给一个C*指针时,自动指针拥有C的所有权(但引用计数保持不变)。
当从一个AutoPtr<C>创建另外一个AutoPtr<C>时,两个自动指针共享C的所有权,引用计数加1。
当把一个AutoPtr<C>赋值给另一个AutoPtr<C>时,两个自动指针共享C的所有权,引用计数加1。
程序示例:
#include "Poco/AutoPtr.h"
using Poco::AutoPtr;
class RCO
{
public:
RCO(): _rc(1)
{
}
void duplicate()
{
++_rc; // Warning: not thread safe!
}
void release()
{
if (--_rc == 0) delete this; // Warning: not thread safe!
}
private:
int _rc;
};
int main(int argc, char** argv)
{
RCO* pNew = new RCO;//_rc == 1
AutoPtr<RCO> p1(pNew);//_rc == 1
AutoPtr<RCO> p2(p1);//_rc == 2
AutoPtr<RCO> p3(pNew, true); //_rc == 3
p2 = 0;//_rc == 2
p3 = 0;//_rc == 1
RCO* pRCO = p1;//_rc == 1
p1 = 0;// _rc == 0 -> deleted
// pRCO and pNew now invalid!
p1 = new RCO;// _rc == 1
return 0;
}
引用计数对象
Poco::RefCountedObject实现了线程安全的引用计数,截至1.3.4在可能的情况下
它使用了平台相关的原子操作。
Poco::RefCountedObject可以作为实现引用计数类的基类。
Poco::RefCountedObject的销毁方法是被保护的(protected),禁止了拷贝构造和赋值。
所有引用计数对象的销毁都应该是被保护的(protected),这样是为了防止显式的使用delete删除。
程序示例:
#include "Poco/RefCountedObject.h"
#include "Poco/AutoPtr.h"
#include <iostream>
using Poco::RefCountedObject;
using Poco::AutoPtr;
class RCO: public RefCountedObject
{
public:
RCO()
{
}
void greet() const
{
std::cout << "Hello, world!" << std::endl;
}
protected:
~RCO()
{
}
};
int main(int argc, char** argv)
{
AutoPtr<RCO> pRCO(new RCO);
pRCO->greet();// AutoPtr has -> operator
(*pRCO).greet(); // AutoPtr has * operator
std::cout << "refcount: " << pRCO->referenceCount() << std::endl;
RCO* p1 = pRCO; // AutoPtr supports conversion to plain pointer
RCO* p2 = pRCO.get();
return 0;
}
自动指针的操作和语义
Poco::AutoPtr支持关系操作符:==, !=, <, <=, >, >=
如果指针为空,解析操作符: *, -> 将抛出NullPointerException异常。
Poco::AutoPtr支持缺省构造、拷贝构造、赋值操作,可以被用于collections集合
(比如std::vector 或者 std::map)
使用AutoPtr::isNull() 或者 AutoPtr::operator ! ()测试是否为空。
自动指针和转换
像普通指针一样,Poco::AutoPtr支持转换操作。
template <class Other> AutoPtr<Other> cast() const
AutoPtr的转换是类型安全的(在内部用的是dynamic_cast,所以非法转换会返回空指针)
支持通过模板构造和赋值操作给兼容的自动指针赋值。
程序示例:
#include "Poco/AutoPtr.h"
#include "Poco/RefCountedObject.h"
class A: public Poco::RefCountedObject {};
class B: public A {};
class C: public Poco::RefCountedObject {};
int main(int argc, char** argv)
{
Poco::AutoPtr<A> pA;
Poco::AutoPtr<B> pB(new B);
pA = pB;
pA = new B; // okay, pB is a subclass of pA
//pB = pA;// will not compile
pB = pA.cast<B>(); // okay
Poco::AutoPtr<C> pC(new C);
pA = pC.cast<A>();// pA is null
return 0;
}
自动指针附加说明和注意点
把AutoPtr赋值给普通指针,然后又把这个普通指针赋值给其他AutoPtr要特别小心!
两个AutoPtr都有对象的所有权,这是不好的设计。
需要共享所有权的情况要明确声明。比如
AutoPtr::AutoPtr(C* pObject, bool shared);
AutoPtr& AutoPtr::assign(C* pObject, bool shared);
shared必须为true!
示例:
#include "Poco/AutoPtr.h"
#include "Poco/RefCountedObject.h"
class A: public Poco::RefCountedObject
{
};
int main(int argc, char** argv)
{
Poco::AutoPtr<A> p1(new A);
A* pA = p1;
//Poco::AutoPtr<A> p2(pA);// BAD! p2 assumes sole ownership
Poco::AutoPtr<A> p2(pA, true); // Okay: p2 shares ownership with p1
Poco::AutoPtr<A> p3;
//p3 = pA;// BAD! p3 assumes sole ownership
p3.assign(pA, true);// Okay: p3 shares ownership with p1
return 0;
}
自动释放池
Poco::AutoReleasePool类模版关注单一应用对象。
#include "Poco/AutoReleasePool.h"
Poco::AutoReleasePool对所有加入自己的对象有所有权。
当Poco::AutoReleasePool被销毁(或release方法被调用),它调用每个对象的release()方法,
释放所有对象的引用。
#include "Poco/AutoReleasePool.h"
using Poco::AutoReleasePool;
class C
{
public:
C()
{}
void release()
{
delete this;
}
};
int main(int argc, char** argv)
{
AutoReleasePool<C> pool;
C* pC = new C;
pool.add(pC);
pC = new C;
pool.add(pC);
return 0;
}
// all C's deleted
共享指针类模板
Poco::SharedPtr对自己没有实现引用计数的类的引用计数功能。
#include "Poco/SharedPtr.h"
Poco::SharedPtr与Poco::AutoPtr有相同的特性(解析、关系操作等)
注意:将指向同一对象的普通指针赋值给不同的Poco::SharedPtr会导致对象有多个所有者,
引起不可预料的行为(也就是崩溃)
一旦对一个对象使用了Poco::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
共享指针操作和语义
Poco::SharedPtr支持比较操作:==, !=, <, <=, >, >=,解析操作: *, ->
如果指针为空,抛出NullPointerException异常。
Poco::SharedPtr支持缺省构造、拷贝构造、赋值构造,可以被用在
std::vector和 std::map这样的集合中。
用SharedPtr::isNull() 或者 SharedPtr::operator ! () 测试是否为空
Poco::SharedPtr支持cast转换,template <class Other> SharedPtr<Other> cast() const
SharedPtr的cast转换是类型安全的,支持通过模板构造和赋值构造给兼容的SharedPtr赋值。
共享指针和数组
共享指针的默认实现是简单的调用delete pObj,但这对通过new[]创建的对象来说是错误的,
应该用delete[] pObj来实现。
要用自定义的释放策略创建共享指针,用以下代码:
SharedPtr<T, ReferenceCounter, ArrayReleasePolicy>
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);
动态工厂类模板
Poco::DynamicFactory支持通过名字创建对象。
#include "Poco/DynamicFactory.h"
DynamicFactory管理的所有类必须有一个共同的基类。DynamicFactory从这个基类进行实例化。
C* DynamicFactory::createInstance(const std::string& name) const;
从给定名字创建C的子类的实例。
要做到这些,类和他们的实例器(工厂类)必须在DynamicFactory中注册。
实例器(Instantiator)类模板
C类实例器通常一定是Poco::AbstractInstantiator<Base>的子类,其中Base是C的基类。
Poco::Instantiator是工厂类的模板。它定义了基本方法createinstance(),这个方法
使用new操作创建类的新实例。
考虑到类继承,一个实例器(Instantiator)总是从定义了createInstance()方法
的Poco::AbstractInstantiator继承。
#include "Poco/DynamicFactory.h"
#include "Poco/SharedPtr.h"
using Poco::DynamicFactory;
using Poco::SharedPtr;
class Base
{
};
class A: public Base
{
};
class B: public Base
{
};
int main(int argc, char** argv)
{
DynamicFactory<Base> factory;
factory.registerClass<A>("A"); // creates Instantiator<A, Base>
factory.registerClass<B>("B"); // creates Instantiator<B, Base>
SharedPtr<Base> pA = factory.createInstance("A");
SharedPtr<Base> pB = factory.createInstance("B");
// you can unregister classes
factory.unregisterClass("B");
// you can also check for the existence of a class
bool haveA = factory.isClass("A"); // true
bool haveB = factory.isClass("B"); // false (unregistered)
bool haveC = factory.isClass("C"); // false (never registered)
return 0;
}
动态工厂和实例器
为了Poco::Instantiator类模板能工作,类必须是可默认构造的。
如果一个类不是可默认构造的,或者它需要特殊的构造步骤,你就必须自己实现实例器类。
#include "Poco/DynamicFactory.h"
#include "Poco/SharedPtr.h"
using Poco::DynamicFactory;
using Poco::SharedPtr;
using Poco::AbstractInstantiator;
class Base
{
};
class A: public Base
{
};
class C: public Base
{
public:
C(int i): _i(i)
{
}
private:
int _i;
};
class CInstantiator: public AbstractInstantiator<Base>
{
public:
CInstantiator(int i): _i(i)
{
}
Base* createInstance() const
{
return new C(_i);
}
private:
int _i;
};
int main(int argc, char** argv)
{
DynamicFactory<Base> factory;
factory.registerClass<A>("A");
factory.registerClass("C", new CInstantiator(42));
SharedPtr<Base> pC = factory.createInstance("C");
return 0;
}
缓冲区(Buffer)管理
当面对已有的C库调用或者系统级调用,经常需要提供特定大小的缓冲区。
如果缓冲区比较大,它就需要在堆(heap)中分配内存。这时候需要对内存进行适当的管理,
保证当不再使用甚至发生异常的时候缓冲区能够被释放。
std::auto_ptr 或者 Poco::SharedPtr (带有默认的释放策略)无法在这里应用,
因为他们不能应用于数组。
缓冲区类模板
Poco::Buffer被用来提供在堆上分配的固定大小的缓冲区,在缓冲对象作用域之外会自动删除缓冲对象。
#include "Poco/Buffer.h"
begin()方法返回指向缓冲区首部的指针,end()方法返回指向尾部的指针。
index操作符提供对单个元素的访问功能。
#include <Poco/Buffer.h>
#include <string>
#include <iostream>
using Poco::Buffer;
int main(int argc, char** argv)
{
Buffer<char> buffer(1024);
std::cin.read(buffer.begin(), buffer.size());
std::streamsize n = std::cin.gcount();
std::string s(buffer.begin(), n);
std::cout << s << std::endl;
return 0;
}
内存池
很多程序都频繁的分配和释放给定大小的缓冲区。在堆内存上分配缓冲有性能上的限制,
并且经常导致堆内存碎片化。因此,已分配缓冲的重复使用很有意义。
Poco::MemoryPool正是完成这项功能。
内存池类
Poco::MemoryPool类容纳固定大小的内存块集合。
#include "Poco/MemoryPool.h"
void* MemoryPool::get()调用传递出一个内存连续区的指针。如果已经没有可用内存,
就新分配一块内存。内存的总大小是有限制的,如果超过最大限制,就抛出OutOfMemoryException异常。
void MemoryPool::release(void* ptr)将内存块释放回内存池中。
单例
单例设计模式用来限制某个类只能实例化一个对象。这对于需要一个全局对象协调整个系统内各操作时是非常必要的。
有时,只有一个对象实例或者对象实例越少,操作越有效率。单例模式也经常被用来作为全局变量的一种委婉的实现
方式。单例模式不可滥用,在把一个类设计为单例之前,要认真考虑设计一个普通类并仅使用一个对象是否就足够。
如果完全肯定必须使用单例,POCO提供了Poco::SingletonHolder类来帮组实现线程安全的“懒汉式单例”管理。
#include "Poco/SingletonHolder.h"
单例对象实例当第一次被请求时在堆内存上创建,当应用结束时被销毁。
单例模式和双重检查锁定机制,记住一定不要使用双重检查锁定机制实现单例模式的线程安全访问。
使用示例
#include "Poco/SingletonHolder.h"
class MySingleton
{
public:
MySingleton()
{
// ...
}
~MySingleton()
{
// ...
}
// ...
};
static MySingleton& instance()
{
static Poco::SingletonHolder<MySingleton> sh;
return *sh.get();
}
};