【问题标题】:Managing destructors with pre-allocated memory and arrays使用预先分配的内存和数组管理析构函数
【发布时间】:2026-01-28 00:35:02
【问题描述】:

你好所以我正在尝试使用预分配内存创建对象和数组。例如,我有以下代码:

int * prealloc = (int*)malloc(sizeof(Test));

Test *arr = new(prealloc) Test();

其中 test 定义如下:

class Test {
public:
    Test() {
        printf("In Constructor\n");
    }
    ~Test() {
        printf("In Destructor\n");
    }

    int val;
};

在这种情况下,如果我调用 delete 它实际上会释放内存,这是不好的,b/c 也许我正在使用某种类型的内存管理器,所以这肯定会导致一些问题。我在互联网上搜索,我发现的唯一解决方案是显式调用析构函数,然后免费调用:

arr->~Test();
free(arr);

还有其他方法可以做到这一点吗?有没有办法调用 delete 并告诉它只调用析构函数而不释放内存?

我的第二个问题是使用数组时,就像前面的示例一样,您可以将预分配的内存传递给 new:

int * prealloc2 = (int*)malloc(sizeof(Test) * 10);
Test *arr2 = new(prealloc2) Test[10];

如果我调用delete[],它不仅会调用数组中每个元素的析构函数,还会释放我不想要的内存。我发现应该这样做的唯一方法是遍历数组并显式调用析构函数,然后调用 free。与常规的非数组运算符一样,有没有办法告诉运算符只调用析构函数而不释放内存?

我确实注意到的一件事是,数组的 new 运算符实际上将使用前 4 个字节来存储数组的大小(我只在 Visual Studio 中使用 32 位构建进行了测试)这将帮助我了解如何数组有很多元素,但仍然存在一个问题。如果数组是指针数组怎么办?例如:

Test **arr2 = new Test*[10];

请有人帮我解决这些问题。

【问题讨论】:

  • 为什么不重载 newdelete 运算符,以便您可以随意分配内存?
  • 我真的没有考虑过重载它们 b/c 我在想实例化类的人应该决定如何映射内存。
  • @Kunashu 您可以使用 Modern C++ 中描述的模板策略吗?

标签: c++ memory-management new-operator


【解决方案1】:

是的,这是唯一的方法。允许定义new 但不允许定义delete 存在不对称性。 [好吧,你可以做后者,但它只能在new抛出异常时被调用(下面没有正确处理!)

您可以使用模板化的destroy 来实现相同的结果:

class Test 
{
public:
    Test() {
        printf("In Constructor\n");
    }
    ~Test() {
        printf("In Destructor\n");
    }

    int val;
};

class Allocator
{
public:
    static void* allocate(size_t amount) { return std::malloc(amount);}
    static void unallocate(void* mem) { std::free(mem);}
    static Allocator allocator;
};

Allocator Allocator::allocator;

inline void* operator new(size_t size,  const Allocator& allocator)
{
    return allocator.allocate(size);
}


template<class T>
void destroy(const Allocator& allocator, T* object) 
{ 
    object->~T();
    allocator.unallocate(object);
}




int main()
{
    Test* t = new (Allocator::allocator) Test();

    destroy(Allocator::allocator, t);

    return 0;
}

【讨论】:

  • 您编写了一个名为Allocator 的对象,它甚至没有完全满足allocator 的概念? en.cppreference.com/w/cpp/concept/Allocator
  • 好的,不是正确的名称。在实际代码中,上述核心Allocator 将有一个反映其实际内存池类型的名称(可能不是malloc/free),我将使用基于以下内容实例化的模板定义一个完整的 C++ 分配器更多。我的观点是destroy 模板可以替换通常的delete
【解决方案2】:

直接调用析构函数来销毁您使用placement new 创建的对象是很正常的。至于任何其他的做事方式,唯一明显的替代方法是使用 Allocator 对象(至少 99% 的情况下,它只是放置 new 并直接调用析构函数的包装器)。

一般来说,您确实想使用new[]。您通常希望使用 operator new(或者可能是 ::operator new)分配原始内存,并使用匹配的 operator delete::operator delete 释放它。

您在该内存中创建具有新位置的对象,并通过直接调用析构函数来销毁它们。

【讨论】:

  • 好的,我认为我必须选择的路线是创建一个包装分配器。问题是 b/c 我在做游戏,我更愿意使用自定义分配器来了解任何内存泄漏。我唯一的问题是为什么我不应该使用new[] 运算符?
  • @Kunashu:基本上,因为new[] 在分配原始内存和使用真实容器之间是一个无用的中间点(就像你显然在尝试写,或者std::vector,@ 987654330@ 等)最终成为一个好的原始分配器做得太多,但不足以成为一个好的容器。
  • 所以如果我想制作自己的容器,你建议我使用 malloc 而不是 new?就像你说的那样,这就是我真正想做的事情。但是,要创建一个容器类,我想添加通过使用策略插入一些自定义分配器的可能性。
  • @Kunashu:如果您正在编写自己的容器,请使用 new char[size] 而不是 malloc。或者更好的是,只需使用像 vector 和朋友这样的分配器。但不要放置新的数组。
  • @Kunashu:哎呀——对不起。另一个我认为值得一读的链接:*.com/a/7151831/179910(虽然我可能有偏见)。
【解决方案3】:

除了显式调用析构函数之外别无他法,因为 delete 也会尝试释放内存。

在您的代码中使用带有新位置的预分配内存应该是相当少见的 - 一个典型的用例是当您想要/需要在固定内存地址上映射对象时处理直接内存映射的硬件接口 -并且是我通常认为是代码异味的东西。

如果您想调整特定类的内存管理,最好使用带有自定义分配器的 STL 容器,或者为该特定类重载 new 和 delete 运算符。

【讨论】:

  • 在预分配内存中放置新的位置对于容器来说很常见。
  • 公平点,我应该详细说明它在“正常”中的使用,即实现代码是我认为除了一些异常之外的代码气味。