记忆领域的例外安全

时间:2011-12-21 15:12:58

标签: c++ c++11 exception-safety

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

{
    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

但是如果内部分配没有被清除,外部分配也无法清除,因为内存竞技场像硬件堆栈一样线性分配它们,所以我泄漏了内存。所以我要么通过尽早销毁一个物体来违反我的语义,要么就是在泄漏记忆。

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

2 个答案:

答案 0 :(得分:2)

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

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

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

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

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

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

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

答案 1 :(得分:0)

通过内存池的语义,并且正如您在问题中所述,只有整个池可以被释放,而不是单个对象。但你想要做到这一点。

可能的情况是为分配器配备brk-like functions以获取和设置下一个分配地址。这为您提供了一种低级机制,您可以使用它来构建任何您想要的内容。