Raz*_*ius 5 c++ memory-management void-pointers dynamic-memory-allocation reinterpret-cast
在 C++ 中实现某些数据结构时,需要能够创建一个包含未初始化元素的数组。正因为如此,拥有
buffer = new T[capacity];
Run Code Online (Sandbox Code Playgroud)
不适合,因为new T[capacity]初始化数组元素,这并不总是可能的(如果 T 没有默认构造函数)或期望的(因为构造对象可能需要时间)。典型的解决方案是分配内存并使用placement new。
为此,如果我们知道元素的数量是已知的(或者至少我们有一个上限)并在堆栈上分配,那么,据我所知,可以使用对齐的字节或字符数组,然后使用std::launder访问成员。
alignas(T) std::byte buffer[capacity];
Run Code Online (Sandbox Code Playgroud)
但是,它只解决了栈分配的问题,并没有解决堆分配的问题。为此,我假设需要使用对齐新,并写这样的东西:
auto memory = ::operator new(sizeof(T) * capacity, std::align_val_t{alignof(T)});
Run Code Online (Sandbox Code Playgroud)
然后将其转换为std::byte*orunsigned char*或T*。
// not sure what the right type for reinterpret cast should be
buffer = reinterpret_cast(memory);
Run Code Online (Sandbox Code Playgroud)
但是,有几件事我不清楚。
reinterpret_cast<T*>(ptr)如果 ptr 指向可与 T 进行指针互转换的对象,则定义结果。(请参阅此答案或https://eel.is/c++draft/basic.types#basic.compound-3)了解更多详细信息。我假设,将其转换T*为无效,因为 T 不一定与 new 的结果是指针可互转换的。但是,它是否为char*或定义明确std::byte?new为有效的指针类型(假设它不是实现定义的)时,它是否被视为指向数组第一个元素的指针,或者只是指向单个对象的指针?虽然,据我所知,在实践中很少(如果有的话)很重要,但存在语义差异,pointer_type + integer只有当指向的元素是数组成员并且算术结果指向另一个时,类型表达式才被很好地定义数组元素。(见https://eel.is/c++draft/expr.add#4)。unsigned char或std::byte可以为新放置的结果提供存储(https://eel.is/c++draft/basic.memobj#intro.object-3),但是它是为其他类型的数组?T::operator new,T::operator new[]表情调用::operator new或::operator new[]幕后。既然builtin的结果new是void,那么如何转换成正确的类型呢?这些是基于实现的还是我们有明确定义的规则来处理这些?::operator delete(static_cast<void*>(buffer), sizeof(T) * capacity, std::align_val_t{alignof(T)});
Run Code Online (Sandbox Code Playgroud)
还是有另一种方法?
PS:我可能会在实际代码中将标准库用于这些目的,但是我试图了解幕后的工作原理。
谢谢。
小智 0
我认为你的前提可能是不正确的。如果 T 是一个类,则应调用默认构造函数。但是,它可以为空,并且如果您的类包含所有 POD(普通旧数据),则不会初始化任何内容。实际上,我一直依赖这一点,因为出于性能原因,我经常不希望对事物进行初始化。
我相信对于全局数据等有一些警告,其中有些东西是零初始化的。但一般来说堆东西不是。你可以测试一下,你会发现内存中有一堆垃圾,至少在发布模式下编译时是这样。一些编译器会在调试模式下初始化内存,但这是在构造函数之外完成的。
例如,您可以在自定义放置新函数中设置数据,如果它是 POD,它仍然会存在于构造函数中。有些人会认为这是 UB,但我认为标准对 POD 说“什么也没做”,这意味着没有初始化。