【发布时间】:2021-06-18 05:10:03
【问题描述】:
任何类型的数组都是implicit-lifetime objects,也可以是begin the lifetime of implicit-lifetime object, without beginning the lifetime of its subobjects。
据我所知,在不以不会导致 UB 的方式开始其元素的生命周期的情况下创建数组的可能性是隐式生命周期对象的动机之一,请参阅http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html。
现在,正确的做法是什么?分配内存并返回指向数组的指针就足够了吗?或者还有什么需要注意的?
也就是说,这段代码是否有效,它是否创建了一个包含未初始化成员的数组,或者我们仍然有 UB?
// implicitly creates an array of size n and returns a pointer to it
auto arrPtr = reinterpret_cast<T(*)[]>(::operator new(sizeof(T) * n, std::alignval_t{alignof(T)}) );
// is there a difference between reinterpret_cast<T(*)[]> and reinterpret_cast<T(*)[n]>?
auto arr = *arrPtr; // de-reference of the result in previous line.
这个问题可以重述如下。
根据https://en.cppreference.com/w/cpp/memory/allocator/allocate,allocate 函数函数在存储中创建T[n] 类型的数组并开始其生命周期,但不会开始其任何元素的生命周期。强>
一个简单的问题 - 它是如何完成的? (忽略constexpr 部分,但我不介意答案中是否也解释了constexpr 部分)。
PS:提供的代码对 c++20 有效(假设它是正确的),但据我所知,不适用于早期标准。
我相信这个问题的答案也应该回答我之前提出的两个类似问题。
- Arrays and implicit-lifetime object creation。
- Is it possible to allocatate uninialized array in a way that does not result in UB。
编辑:我添加了一些代码 sn-ps,以使我的问题更清楚。我将不胜感激,解释哪些是有效的,哪些是无效的。
PS:请随意将malloc 替换为对齐版本,或::operator new 变体。据我所知,这并不重要。
示例 #1
T* allocate_array(std::size_t n)
{
return reinterpret_cast<T*>( malloc(sizeof(T) * n) );
// does it return an implicitly constructed array (as long as
// subsequent usage is valid) or a T* pointer that does not "point"
// to a T object that was constructed, hence UB
// Edit: if we take n = 1 in this example, and T is not implicit-lifetime
// type, then we have a pointer to an object that has not yet been
// constructed and and doesn't have implicit lifetime - which is bad
}
示例 #2。
T* allocate_array(std::size_t n)
{
// malloc implicitly constructs - reinterpet_cast should a pointer to
// suitably created object (a T array), hence, no UB here.
T(*)[] array_pointer = reinterpret_cast<T(*)[]>(malloc(sizeof(T) * n) );
// The pointer in the previous line is a pointer to valid array, de-reference
// is supposed to give me that array
T* array = *array_pointer;
return array;
}
示例 #3 - 与 2 相同,但数组的大小是已知的。
T* allocate_array(std::size_t n)
{
// malloc implicitly constructs - reinterpet_cast should a pointer to
// suitably created object (a T array), hence, no UB here.
T(*)[n] n_array_pointer = reinterpret_cast<T(*)[n]>(malloc(sizeof(T) * n) );
// The pointer in the previous line is a pointer to valid array, de-reference
// is supposed to give me that array
T* n_array = *n_array_pointer;
return n_array;
}
这些都有效吗?
答案
虽然标准的措辞不是 100% 清楚,但在更仔细地阅读论文之后,动机是使转换为 T* 合法,而不是转换为 T(*)[]。 Dynamic construction of arrays。此外,the changes to the standard by the authors of the paper 暗示演员应该是T* 而不是T(*)[]。因此,接受the answer by Nicol Bolas 作为我问题的正确答案。
【问题讨论】:
-
我看到 C++ 不断地从简单到 WTF 的土地。
-
@user14063792468:他所说的“变化”从 C++03 开始就存在了。这不是新的。指针算法仅在对象数组的上下文中定义(单个活动对象被计为 1 元素数组)。如果你只是分配了一些内存,里面没有任何对象,所以你不能只对它进行指针运算。
-
@dvix - 数组是隐式生命周期对象。 eel.is/c++draft/basic.types “标量类型、隐式生命周期类类型 ([class.prop])、数组类型和这些类型的 cv 限定版本统称为隐式生命周期类型”。它说的是数组类型,而没有说空初始化。隐式生命周期的概念是 c++20 标准的新概念,而 vacuous initialization 不是。她们不一样。请注意,隐式生命周期对象(数组)可以具有不是隐式生命周期对象eel.is/c++draft/intro.object#note-3 的子对象。
-
@dvix "某些操作被描述为在指定的存储区域内隐式创建对象。对于指定为隐式创建对象的每个操作,该操作隐式创建并开始零生命周期或更多隐式生命周期类型([basic.types])的对象,如果这样做会导致程序具有已定义的行为" ... "这样的操作不会启动此类对象本身不是隐式生命周期类型的子对象的生命周期".
-
@dxiv:请注意,该问题的某些答案在 C++20 中不再有效。
标签: c++ arrays dynamic-memory-allocation c++20 lifetime