【问题标题】:Exception safety in memory arena内存领域的异常安全
【发布时间】:2012-01-25 09:28:20
【问题描述】:

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

{
    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

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

对于如何解决这个问题有什么建议吗?

【问题讨论】:

  • 什么是MemoryArena?我们怎么能在不知情的情况下说出任何话呢?
  • 目前还不清楚你是如何泄漏内存的,当 arena 被释放时它会被释放。
  • @zvrba:已分配,但无法使用,因为对象从未构造过,指针也从未返回,所以内存一直泄漏,直到区域被销毁。
  • @DeadMG:MemoryArena的实现是什么?该信息不在问题范围内。
  • 不需要实现。这对我来说似乎是一个设计问题。基本上是问如何在不违反某些语义和泄漏内存的情况下实现它。

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


【解决方案1】:

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

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

也许您可以通过“软”分配的概念明确这一点。它不能保证永远存在,因为虽然仍然“柔软”,但它可以被释放回竞技场。然后 X 的构造函数会做这样的事情:

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

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

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

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

【讨论】:

  • 优秀的推荐。我可能会让指针失效。
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-25
    相关资源
    最近更新 更多