问题描述
在C ++中实现某些数据结构时,需要能够创建一个具有未初始化元素的数组.因此,拥有
When implementing certain data structures in C++ one needs to be able to create an array that has uninitialized elements. Because of that, having
buffer = new T[capacity];
不适合,因为 new T [capacity]
会初始化数组元素,这并非总是可能的(如果T没有默认构造函数)或期望的(因为构造对象可能需要时间).典型的解决方案是分配内存并使用new放置.
is not suitable, as new T[capacity]
initializes the array elements, which is not always possible (if T does not have a default constructor) or desired (as constructing objects might take time). The typical solution is to allocate memory and use placement new.
为此,如果我们知道元素的数目是已知的(或者至少我们有一个上限)并且在堆栈上进行分配,那么据我所知,可以使用对齐的字节或字符数组,然后使用 std :: launder
访问成员.
For that, if we know the number of elements is known (or at least we have an upper bound) and allocate on stack, then, as far as I am aware, one can use an aligned array of bytes or chars, and then use std::launder
to access the members.
alignas(T) std::byte buffer[capacity];
但是,它只能解决堆栈分配问题,但不能解决堆分配问题.为此,我假设需要使用对齐的new,并编写如下内容:
However, it solves the problem only for stack allocations, but it does not solve the problem for heap alloations. For that, I assume one needs to use aligned new, and write something like this:
auto memory = ::operator new(sizeof(T) * capacity, std::align_val_t{alignof(T)});
,然后将其强制转换为 std :: byte *
或 unsigned char *
或 T *
.
and then cast it either to std::byte*
or unsigned char*
or T*
.
// not sure what the right type for reinterpret cast should be
buffer = reinterpret_cast(memory);
但是,有几件事我不清楚.
However, there are several things that are not clear to me.
- 如果ptr指向可与T进行指针互转换的对象,则定义结果
reinterpret_cast< T *>(ptr)
(请参阅或 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 []
在幕后.由于内置new
的结果是无效的,如何转换为正确的类型?这些是基于实现的,还是我们有明确定义的规则来处理这些问题? - 释放内存时,应该使用一次
- The result
reinterpret_cast<T*>(ptr)
is defined if ptr points an object that is pointer-interconvertible with T. (See this answer or https://eel.is/c++draft/basic.types#basic.compound-3) for more detail. I assume, that converting it toT*
is not valid, as T is not necessarily pointer-interconvertible with result of new. However, is it well defined forchar*
orstd::byte
? - When converting the result of
new
to a valid pointer type (assuming it is not implementation defined), is it treated as a pointer to first element of array, or just a pointer to a single object? While, as far as I know, it rarely (if at all) matters in practice, there is a semantic difference, an expression of typepointer_type + integer
is well defined only if pointed element is an array member, and if the result of arithmetic points to another array element. (see https://eel.is/c++draft/expr.add#4). - As for lifetimes are concerned, an object of type array
unsigned char
orstd::byte
can provide storage for result of placement new (https://eel.is/c++draft/basic.memobj#intro.object-3), however is it defined for arrays of other types? - As far as I know
T::operator new
andT::operator new[]
expressions call::operator new
or::operator new[]
behind the scenes. Since the result of builtinnew
is void, how conversion to the right type is done? Are these implementation based or we have well defined rules to handle these? - When freeing the memory, should one use
::operator delete(static_cast<void*>(buffer), sizeof(T) * capacity, std::align_val_t{alignof(T)});
还是有另一种方法?
PS:我可能会在真实代码中使用标准库来实现这些目的,但是我试图了解事物在幕后的工作方式.
PS: I'd probably use the standard library for these purposes in real code, however I try to understand how things work behind the scenes.
谢谢.
推荐答案
关于指针的互转换性,使用 T *
或 {[unsigned] char | std :: byte} *
都没有关系.您必须将其强制转换为 T *
才能使用.
Regarding pointer-interconvertibility, it doesn't matter if you use T *
or {[unsigned] char|std::byte} *
. You will have to cast it to T *
to use it anyway.
请注意,您必须(在转换结果上)调用 std :: launder
来访问指向的 T
对象.唯一的例外是创建对象的新放置调用,因为它们尚不存在.手动的析构函数调用不是 例外.
Note that you must call std::launder
(on the result of the cast) to access the pointed T
objects. The only exception is the placement-new call that creates the objects, because they don't exist yet. The manual destructor call is not an exception.
仅当您不使用 std :: launder
时,缺少指针互转换性才是问题.
The lack of pointer-interconvertibility would only be a problem if you didn't use std::launder
.
如果要更加安全,请将指针存储为 {[unsigned] char | std :: byte} *
,并在执行任何指针算术后将其存储为 reinterpret_cast
.
If you want to be extra safe, store the pointer as {[unsigned] char|std::byte} *
and reinterpret_cast
it after peforming any pointer arithmetic.
该标准没有在任何地方说提供存储"是新放置需要的.我认为该术语仅定义为可用于标准中其他术语的定义.
The standard doesn't say anywhere that "providing storage" is required for placement-new to work. I think this term is defined solely to be used in definitions of other terms in the standard.
考虑 [basic.life]/example-2
,其中即使类型 T
不会提供存储",相同类型的 T
.
Consider [basic.life]/example-2
where operator=
uses placement-new to reconstruct an object in place, even though type T
doesn't "provide storage" for the same type T
.
不确定标准对此要说些什么,但是除了 reinterpret_cast
之外,它还能是什么?
Not sure what the standard has to say about it, but what else can it be other than reinterpret_cast
?
您的方法看起来是正确的,但我认为您不这样做"t 必须传递大小.
Your approach looks correct, but I think you don't have to pass the size.
这篇关于是否可以以不导致UB的方式分配未初始化的数组?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!