内存管理容器设计问题 - 项目需要继承
Memory managing container design issue - items require inheritance
我正在设计一个内存管理容器,考虑到性能和易用性,特别是对于游戏开发项目。这是它的当前状态。
我将从源代码中提取最重要的部分。
// Uptr is a typedef for std::unique_ptr
class MemoryManageable {
bool alive{true};
public: bool isAlive() const { return alive; }
};
template<typename T> struct Deleter {
bool operator()(const Uptr<T>& mItem) const { return !mItem->isAlive(); }
};
template<typename T> class MemoryManager {
// T is the type of items being stored and must inherit MemoryManageable
std::vector<Uptr<T>> items;
std::vector<T*> toAdd; // will be added to items on the next refresh() call
Deleter<T> deleter;
void refresh() {
items.erase(std::remove_if(std::begin(items), std::end(items), deleter), std::end(items));
for(const auto& i : toAdd) items.push_back(Uptr<T>(i)); toAdd.clear();
}
void clear() { items.clear(); toAdd.clear(); }
// Del sets alive to false, so that the item will be deleted and deallocated on the next refresh() call
void del(T& mItem) { mItem.alive = false; }
template<typename TType, typename... TArgs> TType& create(TArgs&&... mArgs) { /* creates a new TType* (derived from T) and puts it in toAdd */ }
template<typename... TArgs> T& create(TArgs&&... mArgs) { return create<T, TArgs...>(std::forward<TArgs>(mArgs)...); }
}
你可以在这里看到一个真正的用法。
所需的用法是这样的:
struct Entity : public MemoryManageable {
Manager& manager;
void destroy() { manager.del(*this); }
...
}
struct Mnnager {
MemoryManager<Entity> mm;
void del(Entity& mEntity) { mm.del(mEntity); }
...
}
Manager::update() {
mm.refresh(); // entities with 'alive == false' are deallocated, and entities in 'mm.toAdd' are added to 'mm.items'
for(auto& entity : mm) entity->update(); // entities 'die' here, setting their 'alive' to false
}
这种具有refresh()
的延迟插入设计具有一些很大的优势:
- 它很快
- 即使已经死亡,也可以"杀死"实体
- 实体可以从其他实体创建,因为它们在调用
populate()
之前不会直接存储在项目中
但是,如果不需要继承MemoryManageable
,并且如果有更优雅的方法来删除实体,我会很高兴。
- 有没有办法让
MemoryManager
内部处理alive
布尔值,而不必继承MemoryManageable
,最重要的是,没有任何性能开销? - 有没有一种更优雅的方法可以用来删除
MemoryManager
处理的项目?
理想情况下,MemoryManager
处理的项目应该对此一无所知。
示例用法:在游戏开发中,实体在更新过程中被销毁是很常见的。考虑一个具有 int life 成员的"敌人"实体:if(life <= 0) this->destroy();
- 这在更新循环期间很容易发生,如果实体在销毁时立即从管理器中删除,则会导致循环和其他指向死实体的实体出现问题。
首先要说的是:我不太喜欢C++11,所以我编写的代码中可能存在一些语法错误,但重要的是逻辑。
如果我理解您的问题,您只希望能够在容器中异步添加和删除项目,而那些人不知道它们的状态。在此方案中,可以使用std::map< std::unique_ptr< Elem >, bool >
来处理项的状态:true
= 活动,false
= 活动。
内存管理器类
字段:
-
std::vector< T * > m_toAdd
,一个尚未添加项的向量; -
std::map< std::unique_ptr< T >, bool > m_items
,一张地图,每个项目都管理在一个bool
标志
方法:
-
add()
,在m_toAdd
向量中添加一个新项; -
del()
,使用其标志标记要在m_items
中删除的项目; -
refresh()
,它删除死项目并在m_items
中提交m_toAdd
活动项目,然后清除向量
电子级
字段:
-
MemoryManager & m_manager
,对其内存管理器的引用;
方法:
-
Elem()
, ctor,它调用m_manager::add()
; -
del()
,它调用m_manager::del()
.
创造
创建Elem
时,它会自动将自己添加到其内存管理器中,该管理器将其添加到其m_toAdd
向量中,然后在刷新所有内容时,该向量中的那些Elem
将在std::map
中传递,并与true
布尔值配对(最初标记为活动(。
删除
当一个Elem
要被删除时,它会调用其管理器的del()
方法,该方法只是在其std::map
中将其标记为死,然后当所有内容刷新时,管理器中标记为死的每个Elem
都会被删除,并清除m_toAdd
向量。
(我也建议你使用std::enable_shared_from_this
以便更好地处理ptr,但你必须使用std::shared_ptr
,而不是std::unique_ptr
,但这不会那么糟糕。
我的建议是这样的:
template< class T >
class MemoryManager
{
typedef std::unique_ptr< T > unique_t;
typedef std::map< unique_t, bool > map_t;
typedef std::pair< unique_t, bool > pair_t;
typedef std::vector< T * > vector_t;
public:
void add(T * item)
{
m_toAdd.push_back(item);
}
void remove(T & item)
{
typename map_t::iterator it = m_items.find(item);
if (it != m_items.end() )(* it).second = false;
}
void refresh()
{
// clear dead
typename map_t::iterator it = m_items.begin();
while ((it = std::find_if(it, m_items.end(), isDead)) != m_items.end())
{
m_items.erase(it++);
}
// add new
for(T & item : m_toAdd)
{
m_items.insert(std::make_pair(unique_t(item), true));
}
m_toAdd.clear();
}
void clear()
{
m_items.clear();
m_toAdd.clear();
}
protected:
bool isDead(pair_t itemPair)
{
// true = alive, false = dead
return itemPair.second;
}
private:
map_t m_items;
vector_t m_toAdd;
};
class Entity
{
public:
Entity(MemoryManager< Entity > & manager)
: m_manager(manager)
{
m_manager.add(this);
}
void die()
{
m_manager.remove(this);
}
private:
MemoryManager< Entity > & m_manager;
};
注意:代码没有经过测试,肯定是坏了,重要的是逻辑!
我不完全确定这是你想做的,但让我们试试这个......实体仍然需要 destroy/isdead 函数,因为据我了解,您希望从对象本身显式控制其生命周期。
class Entity{
public:
void destroy(){ dead = true; }
bool isDead(){ return dead; }
private:
bool dead{false};
};
struct EntityPred{
bool operator ()(const Entity* p){
return p->isDead();
}
};
template<typename T, typename T_PRED>
class MemoryManager{
// Deletion and insertion is faster to a list than a vector
// since deletion of entites[0] requires v.size() - 1 copy constructions.
// If you really worry about performance (I wouldn't until I have profiled)
// you can use a memory pool allocator for this.
std::list<std::unique_ptr<T> > entities;
// Buffering up many objects that are deleted sequentially is
// faster in a vector, less calls to new/delete.
// But, a pool backed list might be even faster if you're micro optimizing.
std::vector<T*> toAdd;
public:
void add(T* p){ toAdd.push_back(p); }
void refresh(){
for(auto it = entities.begin(); it != entities.end();){
if( T_PRED(it) )
it = entities.erase(it);
else
it++;
}
// Avoid growing the vector inside the loop since we know the size.
entities.reserve(entities.size() + toAdd.size());
for(auto it : toAdd){
entities.push_back(std::unique_ptr<T>(*it));
}
toAdd.clear();
}
};
MemoryManager<Entity, EntityPred> mgr;
Entity
类不知道管理器,在设计它时不需要任何预防措施,只要确保EntityPred
可以确定是否应该以某种方式删除Entity
即可。
让我的大脑发痒,想出了一个非常...有趣。。。一段代码。提示鼓掌"2013年"(f(最丑陋的黑客奖"的获得者是......"(你已经被警告了!
以下代码在 GCC 4.7 中按预期编译和工作,可能存在错误,您可能需要稍微清理一下......但对于 Entity 类的设计,在容器中使用是完全透明的。没有指向经理的指针,没有"amideadyet"变量等。
基本上,我(滥用(通过使用模板从Entity
继承类来使用析构函数调用顺序。删除实体时,在实体仍然有效时调用继承类的析构函数。它通知管理器(派生类具有指向的指针(,并将 Entity 的内容窃取到另一个对象中,该对象将标识为管理器"已删除",直到调用refresh()
。原始Entity
的销毁仍在继续,但所有昂贵的数据成员都被移动构造函数窃取,并且销毁成功完成,而不会破坏我们的重要数据。
享受:)
#include <memory>
#include <utility>
#include <list>
#include <string>
#include <iostream>
class Entity{
public:
Entity(const std::string& msg){ m=msg;};
// Move constructor required
Entity(Entity&& that){
std::swap(m, that.m);
c = that.c;
}
// Should be virtual
virtual ~Entity(){};
// Do this to release it.
void suicide(){ delete this; }
// Not needed, deleted for safety
Entity(const Entity&) = delete;
void operator = (const Entity&) = delete;
void print(){
std::cout<<m<<" "<<c<<std::endl;
c++;
}
private:
std::string m;
int c{0};
};
template<typename T>
class MemoryManager;
template<typename T>
class MemoryObject{
public:
MemoryObject(MemoryManager<T>* _mgr) : mgr(_mgr) {}
MemoryObject(MemoryObject&& that){
mgr = that.mgr;
}
MemoryObject(const MemoryObject& that){
mgr = that.mgr;
}
virtual ~MemoryObject(){}
void operator = (const MemoryObject&) = delete;
bool isDead(){ return is_dead; }
friend void swap(MemoryObject& a, MemoryObject& b){
std::swap(a.mgr, b.mgr);
std::swap(a.is_dead, b.is_dead);
}
protected:
bool is_dead;
MemoryManager<T>* mgr;
};
template<typename T>
class LiveMemoryObject : public MemoryObject<T>, public T {
public:
template<typename... ARGS>
LiveMemoryObject(MemoryManager<T>* _mgr, const ARGS&... args)
: MemoryObject<T>(_mgr), T(args...)
{}
LiveMemoryObject(const LiveMemoryObject&) = delete;
LiveMemoryObject(LiveMemoryObject&& other) = delete;
virtual ~LiveMemoryObject(){
// Called when Entity did `delete this` but before it is destroyed,
// The manager will move construct (swap) a DeadMemoryObject from *this
// Effectively stealing the contents of Entity before it is destroyed.
// So when this destructor returns, this object is pointing to a dummy Entity
// which is destroyed inplace of the original.
MemoryObject<T>::mgr->remove(this);
}
void operator = (const LiveMemoryObject&) = delete;
};
template<typename T>
class DeadMemoryObject : public MemoryObject<T>, public T {
public:
DeadMemoryObject() = delete;
DeadMemoryObject(const DeadMemoryObject&) = delete;
DeadMemoryObject(LiveMemoryObject<T>&& that)
: MemoryObject<T>(that), T(static_cast<T&&>(that))
{
MemoryObject<T>::is_dead = true;
}
virtual ~DeadMemoryObject(){ /* May you finally R.I.P. Entity! */ }
void operator = (const DeadMemoryObject&) = delete;
};
template<typename T>
class MemoryManager{
private:
friend class LiveMemoryObject<T>;
class Handle{
public:
template<typename... ARGS>
explicit Handle(MemoryManager* mgr, const ARGS&... args)
{
auto o = new LiveMemoryObject<T>(mgr, args...);
data = o;
ptr = o;
}
~Handle(){
delete data;
}
T* operator -> (){
return ptr;
}
const T* operator -> () const {
return ptr;
}
private:
void ageData(){
auto p = new DeadMemoryObject<T>(std::move(*dynamic_cast<LiveMemoryObject<T>*>(data)));
data = p;
ptr = p;
}
friend class MemoryManager;
MemoryObject<T>* data; // Memory owned by handle
T* ptr; // ease of access to T* of data.
};
typedef std::shared_ptr<Handle> HandlePtr;
std::list<HandlePtr> items;
std::list<HandlePtr> to_add;
void remove(LiveMemoryObject<T>* p){
for(auto it = items.begin(); it != items.end(); ++it){
if( (*it)->data == p ){
(*it)->ageData();
break;
}
}
}
public:
template<typename... ARGS>
HandlePtr create(const ARGS&... args){
HandlePtr h{std::make_shared<Handle>(this, args...)};
to_add.push_back(h);
return h;
}
void refresh(){
for(auto it = items.begin(); it != items.end();){
if((*it)->data->isDead()){
delete (*it)->data;
(*it)->data = nullptr;
(*it)->ptr = nullptr;
it = items.erase(it);
}
else
it++;
}
for(auto it : to_add){
items.push_back(it);
}
}
};
int main(){
MemoryManager<Entity> mgr;
auto e = mgr.create("Hello world!");
mgr.refresh();
(*e)->print(); // "Hello world! 0"
(*e)->print(); // "Hello world! 1"
(*e)->suicide();
// The object is still valid, we have not called refresh() yet to destroy it.
(*e)->print(); // "Hello world! 2"
mgr.refresh();
(*e)->print(); // Expected sig fault as object has been removed
return 0;
}
<</div>
div class="answers"> 给出的答案并不让我满意,所以我决定保留所需的继承解决方案。尽管它可能很烦人,但它是有效的(没有额外的运行时成本(并且易于实现/维护。
当前实现:SSVUtils/MemoryManager/MemoryManager.h
- 继承函数的重载解析
- 使用C++库在Android项目中修改gradle中的cmake参数,用于插入指令的测试
- 无法在 CLion 中构建 C++ 项目
- 运行同一解决方案的另一个项目的项目
- CMake-按正确顺序将项目与C运行时对象文件链接
- 如何在选项卡视图Qt中设置一个新项目,并保存以前的项目
- C++错误LNK2005和不同项目文件的常规继承
- Omnet++ 如何从同一工作区中不同项目中的 cSimpleModule 继承
- 在另一个项目中包括继承的类时,抽象类的链接器错误
- 我得到了一个使用 SDL 的 OOP 项目。我没有使用继承。我可以给我的评估员什么理由
- C++ 项目设计 - 私有继承和"is a"关系
- 使用C++继承无法访问的项目
- 使用CMake构建Qt项目并从QMainWindow继承会导致未引用的vtable错误
- 有关继承的项目系统问题
- 如何在Visual Studio中将项目依赖项/引用从一个项目继承到依赖项目
- 使用继承处理跨项目的事件
- Visual Studio 2010 C++是否可以从引用的项目继承include路径
- 内存管理容器设计问题 - 项目需要继承
- Visual Studios (2012 pro 主要是) <从父级继承或项目默认值的默认值的文件位置>
- 是否有任何方法可以在项目上创建完整的包含图和继承图