1

我正在编写一个简单的内存区域分配器,并面临一个异常安全的小问题。这种情况是当您分配一个本身调用分配器的对象时。内存池的目标是一次分配一堆对象,然后在池被销毁时将它们全部删除。

{
    MemoryArena m;
    std::string* ptr = m.Allocate<std::string>();
    // use ptr whatever
    // Cleaned up when pool is destroyed
}

但是当它被多次使用时,这变得相当讨厌。如果内部分配被清理,那么它可以在之后使用 - 不错的假设,因为池的定义是在其生命周期结束之前永远不会删除对象。考虑:

struct X {
    X(MemoryArena* ptr, std::string*& ref) {
        ref = ptr->Allocate<std::string>();
        throw std::runtime_error("hai");
    }
};
MemoryArena m;
std::string* ptr;
m.Allocate<X>(&m, ptr);
// ptr is invalid- even though it came from the arena 
// which hasn't yet been destroyed

但是如果内部分配清理,外部分配也无法清理,因为内存区域像硬件堆栈一样线性分配它们,所以我泄漏内存。所以要么我通过提前销毁一个对象来违反我的语义,要么我泄漏内存。

有关如何解决此问题的任何建议?

4

2 回答 2

2

也许这只是适用的示例代码,但我认为用户不应该假设在throwsptr的构造函数时它是有效的。X它也可以在分配 ref 之前抛出。

因此,如果可以的话,我会说清理内部对象(即,如果竞技场的前部当前位于内部对象的末端)。好的,来自竞技场的分配变得无效,这是不正常的,但这是一个无论如何都不应该进入现实世界的分配。

也许您可以使用“软”分配的概念来明确这一点。它不能保证永远存在,因为虽然仍然“柔软”,但它可以被释放回竞技场。然后 X 的构造函数会执行以下操作:

SoftPtr<std::string> tmp(ptr->SoftAllocate<std::string>());
stuff_that_might_throw(); 
ref = tmp.release();

SoftPtr在没有第一次调用的情况下执行的析构函数release意味着没有公开对该对象的引用。它调用 MemoryArena 的一个函数,该函数执行以下操作:

  • 破坏对象
  • 检查这是否是来自竞技场的最新分配
    • 如果是这样,从竞技场的当前位置指针中减去大小
    • 如果没有,什么也不做(内存泄露)

因此,只要以相反的顺序完成,任何数量的分配都可以“退出”。

于 2011-12-21T18:16:22.267 回答
0

根据内存池的语义,正如您在问题中所说的那样,只能释放整个池,而不是单个对象。然而你想这样做。

一种可能性是为分配器配备类似 brk 的功能,以获取和设置下一个分配地址。这为您提供了一种低级机制,您可以使用该机制在其之上构建您想要的任何东西。

于 2011-12-21T18:34:15.473 回答