问题描述
我一直试图找到一种方法来在我的项目中实现对象池。我在互联网上找到了一些例子,其中大部分使用 std::lists 和书中的一个例子;也使用 std::list 的游戏开发模式和最佳实践。
不过,有人告诉我,将列表用于对象池是毫无意义的,因为将对象返回到池中意味着必须再次分配内存。
我也看到有人使用 std::stack,但我认为存在同样的问题。
这是我按照本书示例为对象池类编写的代码:
/* This object has only an int data type (value).
One method to se it and another one to print it. */
class Object
{
public:
Object()
{
SetValue();
std::cout << "Constructed/Reset." << std::endl;
}
// copy constructor
Object( Object& other )
{
this->mValue = other.mValue;
std::cout << "Constructed." << std::endl;
}
~Object()
{
std::cout << "Destroyed." << std::endl;
}
void SetValue( int val = -1 )
{
mValue = val;
}
void PrintValue()
{
std::cout << this->mValue << std::endl;
}
private:
int mValue;
};
class Pool
{
public:
Pool()
{
std::cout << "Pool created!\n";
}
~Pool()
{
std::cout << "Pool destroyed!\n";
}
void Fill( int size )
{
for( int i = 0; i < size; i++ )
{
Object* obj = new Object();
pool.push_back( obj );
}
std::cout << "Objects created!" << "\nObjects in pool: "
<< pool.size() << std::endl;
}
Object* AquireObject()
{
if( !pool.empty() )
{
Object* obj = pool.back();
pool.pop_back();
std::cout << "Object aquired!"
<< "\nNumber of objects in pool: " << pool.size() << std::endl;
return obj;
}
else
{
std::cout << "Object created and aquired!"
<< "\nNumber of objects in pool: " << pool.size() << std::endl;
return new Object();
}
}
void ReleaSEObject( Object* returningObject )
{
returningObject->SetValue();
pool.push_back( returningObject );
std::cout << "Object returned to the pool!"
<< "\nNumber of objects in pool: " << pool.size() << std::endl;
}
void Clear()
{
while( !pool.empty() )
{
Object* obj = pool.back();
pool.pop_back();
delete obj;
}
std::cout << "Objects deleted!"
<< "\nNumber of objects in pool: " << pool.size() << std::endl;
}
private:
std::list<Object*> pool;
};
我也做了一个稍微不同的版本,它使用唯一的指针:
/* This object has only an int data type (value).
One method to se it and another one to print it. */
class Object
{
public:
Object()
{
SetValue();
std::cout << "Constructed/Reset." << std::endl;
}
// copy constructor
Object( Object& other )
{
this->mValue = other.mValue;
std::cout << "Constructed." << std::endl;
}
~Object()
{
std::cout << "Destroyed." << std::endl;
}
void SetValue( int val = -1 )
{
mValue = val;
}
void PrintValue()
{
std::cout << this->mValue << std::endl;
}
private:
int mValue;
};
// Object pool using std::list
class Pool
{
public:
Pool() = default;
~Pool() = default;
/* Fills the pool (list) with a given size using an Object class (prevIoUsly defined)
as a reference using its copy constructor.*/
void Fill( int size,Object* obj )
{
for( int i = 0; i < size; i++ )
{
std::unique_ptr<Object> object = std::make_unique<Object>( *obj );
pool.push_back( std::move( object ) );
}
}
// Clears all items in the list.
void PoolIsClosed()
{
pool.clear();
}
// Returns an instance of an object within the list.
std::unique_ptr<Object> Getobject()
{
if( !pool.empty() )
{
std::unique_ptr<Object> object = std::move( pool.front() );
pool.pop_front();
std::cout << "Object retrieved." << "\nObjects in pool: "
<< pool.size() << std::endl;
return object;
}
else
{
/* "WARNING: NOT ALL CONTROL PATHS RETURN A VALUE." */
std::cout << "Pool is empty." << std::endl;
}
}
// Returns an instance back to the object list.
void returnToPool( std::unique_ptr<Object> returningObject )
{
pool.push_back( std::move( returningObject ) );
std::cout << "Object returned." << "\nObjects in pool: "
<< pool.size() << std::endl;
}
private:
std::list<std::unique_ptr<Object>> pool;
};
我想知道这样的实现是否真的毫无意义,如果是,是否有一种简单的方法来实现基本的对象池类?
我知道boost库有自己的对象池实现……你们有没有人使用过它?我想了解更多,难用吗?..
我还发现了这个对象池库:https://github.com/mrDIMAS/SmartPool,它看起来很有前途,而且很容易使用,但我不知道它到底是如何工作的,我理解起来有点复杂。对此有何意见?
解决方法
如果你不介意滚动你自己的链表逻辑,你可以创建一个不使用任何数据结构的 ObjectPool 类,因此保证不访问堆(除非它的空闲列表是空,它必须这样做才能返回一个新对象,当然)。像这样(警告,几乎没有测试过的代码,可能包含错误):
#include <stdio.h>
template <typename Object> class ObjectPool
{
public:
ObjectPool() : _head(NULL),_tail(NULL) {/* empty */}
~ObjectPool()
{
while(_head)
{
ObjectNode * next = _head->_next;
delete _head;
_head = next;
}
}
Object * AcquireObject()
{
if (_head)
{
ObjectNode * oldNode = _head; // what we're going to return to the user
// Pop (oldNode) off the front of the free-list
if (_head == _tail) _head = _tail = NULL;
else
{
_head = _head->_next;
if (_head) _head->_prev = NULL;
}
return &oldNode->_object;
}
else
{
// There's nothing in our free-list to re-use! Hand the user a new object instead.
ObjectNode * newNode = new ObjectNode;
return &newNode->_object;
}
}
// Note: (obj) *must* be a pointer that we previously returned via AcquireObject()
void ReleaseObject(Object * obj)
{
// reset (obj) back to its default-constructed state
// (this might not be necessary,depending what your Objects represent)
(*obj) = Object();
ObjectNode * node = reinterpret_cast<ObjectNode *>(obj); // this works only because _object is the first item in the ObjectNode struct
if (_head)
{
// Prepend our node back to the start of our free-nodes-list
node->_prev = NULL;
node->_next = _head;
_head->_prev = node;
_head = node;
}
else
{
// We were empty; now will have just this one ObjectNode in the free-list
_head = _tail = node;
node->_prev = node->_next = NULL;
}
}
private:
struct ObjectNode
{
Object _object; // note: this MUST be the first member-variable declared in the ObjectNode struct!
ObjectNode * _prev;
ObjectNode * _next;
};
ObjectNode * _head; // first item in our free-nodes-list,or NULL if we have no available free nodes
ObjectNode * _tail; // last item in our free-nodes-list,or NULL if we have no available free nodes
};
// unit test
int main(int,char **)
{
ObjectPool<int> test;
int * i = test.AcquireObject();
printf("i=%p\n",i);
test.ReleaseObject(i);
return 0;
}