使用自定义分配器调用对象构造函数/析构函数

Callling object constructor/destructor with a custom allocator

本文关键字:构造函数 析构函数 对象 调用 自定义 分配器      更新时间:2023-10-16

我一直在研究自定义分配器,我经常看到它们使用某种函数来分配内存。为了测试目的和进一步教育我自己,我试图做一个"简单"的例子。但是,关于如何做,我理解一件基本的事情。mallocnew 的主要区别之一是,使用 new 调用构造函数。如果我想编写自己的分配器,本质上是替换new,如何在使用malloc时调用构造函数?

我知道在类

上我可以重载类newdelete,所以我想问题的很大一部分是,new如何在分配期间调用对象构造函数?同样,我对delete如何调用析构函数感兴趣。

我创建了一个示例测试代码,我希望在分配期间调用 SomeClass 构造函数,但我看不出如何。

#include <malloc.h>
void* SomeAllocationFunction(size_t size) {
    return malloc(size);
}
class SomeClass
{
public:
    SomeClass() {
        int con = 1000;
    }
    ~SomeClass() {
        int des = 80;
    }
};
int main(void){
    SomeClass* t = (SomeClass*)SomeAllocationFunction(sizeof(SomeClass));
    return 0;
}

(请注意,我知道我可以使用new。但是,出于学习目的,我正在尝试创建一个自定义分配器,它不只是调用newplacement new)。

本质上,当您使用新表达式时: T *t = new T; ,它大致相当于:

void *temp = operator new(sizeof(T));
T *t = new(temp) T;

因此,首先它使用分配函数分配一些原始内存,然后在该内存中构造一个对象。同样,当您使用删除表达式时,例如: delete t; ,它大致相当于:

t->~T();
operator delete(t);

因此,如果您重载特定类的newdelete

class T { 
    int data; 
public:
    // I've made these static explicitly, but they'll be static even if you don't.
    static void *operator new(size_t size) { 
        return malloc(size);
    }
    static void operator delete(void *block) { 
        free(block);
    }
};

然后当你使用一个新表达式时,它会调用类的operator new来分配内存,这将调用malloc,所以T *t = new T();最终将通过malloc分配内存(同样,当你delete它时,它将使用 operator delete ,这将调用free)。

至少在通常使用该术语时,分配器非常相似,只是它由容器而不是其他代码使用。它还将分配函数和删除函数封装到一个类中,因此当您将一个对象传递给容器时,您只需要传递一个对象,并且分配和删除函数不匹配的可能性很小。

暂时忽略有关用于事物的名称的详细信息,标准库中的 Allocator 类大多做同样的事情,因此只需对上面 T 类中的函数进行一些重命名,您大约完成了编写标准分配器的一半。为了进行分配和删除,它有一个功能来rebind一些内存(将内存块更改为另一种类型),就地创建一个对象(基本上只是围绕放置新位置的包装器)和销毁一个对象(再次,围绕析构函数调用的琐碎包装器)。当然,它使用operator newoperator delete,而不是像我上面使用的那样mallocfree

使用放置 new,您可以将已分配的内存位置传递给新运算符。然后 new 将在给定位置构造对象,而无需对自身进行分配。

编辑:

这是它的实现方式:

int main(void){
    // get memory
    void * mem_t = SomeAllocationFunction(sizeof(SomeClass));
    // construct instance
    SomeClass* t = new(mem_t) SomeClass;
    // more code
    // clean up instance
    t->~SomeClass();
    return 0;
}

要调用构造函数,请使用放置 new(注意你不能覆盖放置 new)。对于删除和所有陷阱,常见问题解答很好地解释了它。