【问题标题】:How to prevent destructors from being called on objects managed by boost::fast_pool_allocator?如何防止在 boost::fast_pool_allocator 管理的对象上调用析构函数?
【发布时间】:2014-05-21 10:58:28
【问题描述】:

我想利用boost::fast_pool_allocator 的以下广告功能(请参阅the Boost documentation for Boost Pool):

例如,您可能遇到想要分配一个 一堆小物体在一个点,然后到达你的一个点 不再需要它们的程序。使用池接口, 你可以选择运行它们的析构函数或者只是把它们放到 遗忘...

(请参阅 here 获取此报价。)

关键短语是让他们被遗忘。我想要在这些对象上调用析构函数。

(原因是我有数百万个微小的对象,它们在堆上形成了一个极其复杂的所有权网络,当单个父级对象离开时,我的程序需要大约 20 分钟来调用所有的析构函数堆栈。我不需要调用这些析构函数,因为没有预期的副作用,并且所有内存都包含在 boost::pool 中。)

不幸的是,尽管有上述文档的承诺,以及boost::pool 概念的承诺,我还是找不到阻止托管对象的析构函数被调用的方法。

问题很容易在一个小示例程序中分离出来:

class Obj
{
public:
    ~Obj()
    {
        // Placing a breakpoint here indicates that this is *always* reached
        // (except for the crash scenario discussed below)
        int m = 0;
    }
};

typedef std::map<int, Obj, std::less<int>,
                 boost::fast_pool_allocator<std::pair<int const, Obj>>>
        fast_int_to_int_map;

class Foo
{
public:
    ~Foo()
    {
        // When the following line is uncommented, the program CRASHES
        // when the destructor is exited - because the Obj destructors
        // are called on the invalid Obj ghost instances

        //boost::singleton_pool<boost::fast_pool_allocator_tag,
        //                  sizeof(std::pair<int const, Obj>)>::purge_memory();
    }

    fast_int_to_int_map mmap;
};

void mfoo()
{
    // When this function exits, the Foo instance goes off the stack
    // and its destructor is called, in turn calling the destructors
    // of the Obj instances - this is NOT desired!

    Foo foo;
    foo.mmap[0] = Obj();
    foo.mmap[1] = Obj();
}

int main()
{
    mfoo();

    // The following line deallocates the memory of the pool just fine -
    // but does nothing to prevent the destructors of the Obj instances
    // from being called

    boost::singleton_pool<boost::fast_pool_allocator_tag,
                          sizeof(std::pair<int const, Obj>)>::purge_memory();
}

如代码 cmets 中所述,始终调用由 boost::pool 管理的 Obj 实例的析构函数。

我能做些什么来让 Boost Pool 文档中的有希望的引述 drop them off into oblivion 成真?

【问题讨论】:

    标签: c++ c++11 boost pool


    【解决方案1】:

    默认情况下,池使用default_user_allocator_new_delete 分配器。这将通过首先调用析构函数然后回收底层内存来破坏底层对象。 default_user_allocator_malloc_free 将导致 malloc 的内存在不触发析构函数的情况下被回收 - 因此 drop[ing] them off into oblivion

    也就是说,如果您的树真的那么复杂,那么使用 free 而不是触发析构函数似乎是一种非常好的方法,可以开始从您自己的下方切出分支并可能开始泄漏您无法再访问的内存。

    【讨论】:

    • 只有一个顶级对象,并且作为容器的每个数据成员都使用自定义分配器,依此类推,通过所有嵌套对象,因此在树的层次结构中没有单一路径那是错过了。然后我只是让单个顶级对象从堆栈中删除,its 析构函数在所有内存池上调用purge。非常感谢这个答案 - 回想起来很明显!当然,这就是malloc/freenew/delete 的最大区别——前者不调用构造函数和析构函数。我从不使用malloc/free,所以没想到。
    • 我现在正在尝试这个 - 你知道boost::default_user_allocator_malloc_free分配器确保构造函数被调用吗?
    • 我不知道-在您的示例中,您明确调用了构造函数。我想你会在你的实现中做类似的事情。
    • 举个例子会很有用 - 不幸的是,即使使用boost::default_user_allocator_malloc_free,我的析构函数似乎仍然被调用。
    【解决方案2】:

    您将自定义分配器传递给您的 std::map 类。因此,此分配器将用于std::map 内的所有内容:用于所有Obj 数据以及std::map 二叉树的节点。结果,如果你在Foo的析构函数中调用purge_memory(),那么这部分内存就会失效,并在std::map析构函数中崩溃。

    您认为自定义分配器负责对象解除分配的假设是不正确的:释放所有对象是std::map 的任务。因此,无论您传递自定义分配器还是使用默认分配器,都会调用~Obj()

    对于您的问题,我没有看到任何优雅的解决方案。但是这种方法应该可行:

    • 使用placement new 从池的内存中创建Foo 对象,
    • 照常使用这个Foo对象,
    • 调用purge_memory() 释放池中的所有内存。不会调用 ~Obj~std::map 的析构函数。

    【讨论】:

    • 谢谢!我得出了同样的结论——关于自定义分配器负责内存的定位,而不是调用构造函数或析构函数这一事实。我现在很清楚,这是两个完全独立的过程。即,(1) 使用系统 SDK 内存调用 new/deletemalloc/free 分配/释放内存本身; (2) ctor's 和 dtor's 的调用。自定义分配器负责前者 (#1),而不负责后者。
    • 然而,我的困惑的主要部分来自于在上面的代码中,函数purge_memory()什么都不做。如果它是 set,而不是 mappurge_memory 将释放内存。但是,当map(内部)在自定义分配器上调用new 时,map 创建的内部对象的 size 不是一对。这就是上面purge_memory() 什么都不做的原因。 (在set 的情况下,内部new 请求的大小包含的对象的大小,因此它适用于这种情况。)我现在正在处理后一个问题.
    【解决方案3】:

    此问题的答案包含在@qehgt 答案下方的 cmets 中,详细信息请参见 this new question

    即:

    在对象实例化和删除的两个相关方面之间有一个明确而正式的区别:

    • (1) 内存的分配和释放

    • (2) 构造函数和析构函数的调用

    自定义分配器的用途仅是 (1)。

    容器对象句柄 (2),它们调用自定义分配器中的函数来确定构造对象的内存位置,并告诉自定义分配器可以为给定对象释放分配的内存。 但是对构造函数/析构函数本身的调用是由容器进行的,而不是由自定义分配器进行的。

    因此,实现本题目的的方法如下:通过new声明容器对象,并在其上NEVER CALL delete(但使用自定义分配器保证你后面创建的对象在容器存储在内存池中,然后通过调用purge_memory()自己手动释放内存池):

    class Obj { // has operator<() for use with std::set };
    
    typedef std::set<Obj, std::less<Obj>, boost::fast_pool_allocator<Obj>> fast_set_obj;
    
    // Deliberately do not use a managed pointer -
    // I will *NOT* delete this object, but instead
    // I will manage the memory using the memory pool!!!
    fast_set_obj * mset = new fast_set_obj;
    
    // ... add some Obj's to 'mset'
    mset->insert(Obj());
    mset->insert(Obj());
    
    // Do something desireable with the set ...
    ...
    
    // All done.
    // It's time to release the memory, but do NOT call any Obj destructors.
    // The following line of code works exactly as intended.
    
    boost::singleton_pool<boost::fast_pool_allocator_tag, sizeof(Obj const)>::purge_memory();
    

    (上面的代码取自上面的链接问题。)

    但是,我仍然有一个问题,与当前问题背后的意图没有直接关系:如果使用 map 而不是 set,则上面的代码不起作用。详情请见linked question

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-24
      • 2016-11-08
      • 2014-01-15
      • 1970-01-01
      • 1970-01-01
      • 2010-12-28
      • 2014-10-24
      • 2020-04-29
      相关资源
      最近更新 更多