有没有一种方法来实现与使用 new 运算符调用默认构造函数相同的行为?

huangapple go评论101阅读模式
英文:

Is there a way to implement the same behaviour of the new operator calling default constructors?

问题

我想要实现自己的堆分配函数,以在分配类类型元素时与C++的new运算符执行相同的操作。我的分配函数(用于Windows)不使用new运算符或标准库,而仅使用HeapAlloc。所以我的问题是在分配类类型元素时:

// 分配函数定义。
template <typename Type>
Type* Alloc(int Count) {
  return reinterpret_cast<Type*>(HeapAlloc(GetProcessHeap(), 0, sizeof(Type) * Count));
}

// 使用分配函数。
Alloc<Class>(5);

该函数分配了5个Class,它是一个简单的类,具有一个默认构造函数。构造函数没有被调用,我能理解为什么。我所做的唯一的事情就是分配一些内存并使用reinterpret_cast

然而,我感到困惑的地方在于new运算符:

new Class[5];

默认构造函数被调用了!最终(在Windows上)new运算符也调用了HeapAlloc。该函数还做了其他一些事情,但似乎没有对最终结果产生影响。

new如何能够调用每个分配元素的构造函数,我是否可以做同样的事情?还是说这只是某种内置功能(尽管它是标准库的一部分)?

英文:

I want to implement my own heap allocation function to do the same as the C++ new operator when it comes to allocating elements of a class type. My allocation function (for Windows) does not use the new operator or the standard library, but only HeapAlloc. So my problem is when allocating elements of a class type :

// Allocation function definition.
template &lt;typename Type&gt;
Type* Alloc(int Count) {
  return reinterpret_cast&lt;Type*&gt;(HeapAlloc(GetProcessHeap(), 0, sizeof(Type) * Count));
}

// Allocation function use.
Alloc&lt;Class&gt;(5);

The function allocates 5 Class, which is a simple class with a single default constructor. The constructor does not get called, and I can see why. The only thing I do is allocate some memory and reinterpret_cast it.

Where I get confused though, is with the new operator :

new Class[5];

The default constructor gets called! The new operator is in the end (on Windows of course) also calling HeapAlloc. The function does some other stuff too, but none of it seems to really have an impact on the final result.

How can new call the constructors of each element it allocates, and can I do the same? Or is this just some built-in feature (even though it's part of the standard library)?

答案1

得分: 1

所谓的“新表达式”(new int[5])调用了operator new(类似于malloc)和对象的构造函数。您可以使用“定位new表达式”或C++20中的std::construct_at来手动调用构造函数。您的代码可能如下所示:

// 原始内存分配函数
void* AllocBytes(size_t size, size_t align) {
    void* p = HeapAlloc(GetProcessHeap(), 0, align);
    assert(reinterpret_cast<uintptr_t>(p) % align == 0 &&
           "无效的对齐");
    return p;
}

// 原始内存释放函数
void DeallocBytes(void* p, size_t size, size_t align) {
    // 使用Windows API来释放内存
}

// 分配和构造函数
template <typename Type>
Type* Alloc(size_t Count) {
    // 为'Count'个元素分配内存
    Type* array = reinterpret_cast<Type*>(AllocBytes(Count * sizeof(Type), alignof(Type)));
    // 在内存缓冲区中构造'Count'个元素
    for (size_t i = 0; i < Count; ++i) {
        std::construct_at(&array[i]); // 来自C++20的<memory>

        // 或者使用定位new表达式
        ::new (&array[i]) Type();
    }
    return array;
}

// 销毁和释放函数
template <typename Type>
void Dealloc(Type* p, size_t Count) {
    // 在内存缓冲区中销毁'Count'个元素
    for (size_t i = 0; i < Count; ++i) {
        std::destroy_at(&array[i]); // 来自C++17的<memory>

        // 或者显式调用析构函数
        array[i].~Type();
    }
    DeallocBytes(p, Count * sizeof(Type), alignof(Type));
}

然后,您可以像这样使用这些函数:

// 分配5个元素
Class* p = Alloc<Class>(5);

// 释放5个元素
Dealloc(p, 5);

请注意,使用std::construct_at/std::destroy_at或定位new表达式/手动调用析构函数并没有真正区别。std::construct_atstd::destroy_atconstexpr,更加显式或者更容易理解,但您可以两种方式都使用。

英文:

So called new expressions (new int[5]) call operator new (similar to malloc) and the objects' constructor. You can use placement new expressions or std::construct_at from C++20 to call the constructors manually. Your code could end up looking like this:

// Raw memory allocation function
void* AllocBytes(size_t size, size_t align) {
    void* p = HeapAlloc(GetProcessHeap(), 0, align);
    assert(reinterpret_cast&lt;uintptr_t&gt;(p) % align == 0 &amp;&amp; 
           &quot;Invalid alignment&quot;);
    return p;
}

// Raw memory deallocation function
void DeallocBytes(void* p, size_t size, size_t align) {
    // Use Windows API to deallocate memory
}

// Allocation AND construction function 
template &lt;typename Type&gt;
Type* Alloc(size_t Count) {
    // Allocate memory for &#39;Count&#39; elements
    Type* array = reinterpret_cast&lt;Type*&gt;(AllocBytes(Count * sizeof(Type), alignof(Type)));
    // Construct &#39;Count&#39; elements in the memory buffer
    for (size_t i = 0; i &lt; count; ++i) {
        std::construct_at(&amp;array[i]); // From &lt;memory&gt; in C++20

        // _Or_ a placement new expression
        ::new (&amp;array[i]) Type();
    }
    return array;
}

// Destruction AND deallocation function 
template &lt;typename Type&gt;
void Dealloc(Type* p, size_t Count) {
    // Destroy &#39;Count&#39; elements in the memory buffer
    for (size_t i = 0; i &lt; count; ++i) {
        std::destroy_at(&amp;array[i]); // From &lt;memory&gt; in C++17

        // _Or_ explicit destructor calls
        array[i].~Type();
    }
    DeallocBytes(p, Count * sizeof(Type), alignof(Type));
}

Then you can use the functions like this:

// Allocate 5 elements
Class* p = Alloc&lt;Class&gt;(5);

// Deallocate 5 elements
Dealloc(p, 5);

Note that using std::construct_at/std::destroy_at or placement new expressions/manual destructor calls doesn't really make a difference. std::construct_at and std::destroy_at are constexpr and more explicit or perhaps easier to understand, but you can do it either way.

huangapple
  • 本文由 发表于 2023年7月31日 18:37:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76802805.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定