使用std::launder获取一个动态数组
以下链接中的一个回答让我对如何从一个存储中检索在该存储内部构造的动态数组产生了疑问。
The linked question explains how to do that for a static-sized array but not for a dynamic-sized one. 链接中的问题解释了如何对静态大小的数组实现这一点,但对于动态大小的数组却没有说明。
The problem for dynamic array is evoked here but the issue is bypassed by returning only the address of the elements, not the array. 关于动态数组的问题在这里被提及,但通过仅返回元素的地址而不是数组本身来绕过了这个问题。
Here is a snippet to illustrate:
#include <cstddef>
#include <memory>
#include <stdexcept>
#include <new>
bool is_aligned_to(void* ptr, std::size_t alignment) noexcept {
void* p{ptr};
std::size_t sz{1};
return (std::align(alignment, sz, p, sz) == ptr);
}
void MakeDouble(void* location,std::size_t n) {
if (!is_aligned_to(location, alignof(double))) {
throw std::invalid_argument("bad alignment");
}
new (location) double[n];
}
int main(int argc, const char**) {
static constexpr std::size_t N = 1000 * sizeof(double);
std::byte static_storage[N];
MakeDouble(static_storage,argc); // to make sure the actual size is only known at runtime
[[maybe_unused]] double *arr = std::launder(reinterpret_cast<double *>(static_storage));
}
Is std::launder(reinterpret_cast<double *>(static_storage)); enough to retrieve a real array (in the sense that pointer arithmetic is valid) or does it returns the address of a single element?
std::launder(reinterpret_cast<double *>(static_storage)); 是否足以检索一个真正的数组(在指针运算有效的意义上),还是仅返回一个元素的地址?
This is a slightly different version where I don't explicitly create an array:
void MakeDouble(void* location, std::size_t n) {
if (!is_aligned_to(location, alignof(double))) {
throw std::invalid_argument("bad alignment");
}
for (size_t i = 0;i<n;++i)
{
std::construct_at(reinterpret_cast<double *>(location)+i,3.14);
}
}
I'm building contiguous objects of same type but as arrays are implicit lifetime types, an array of double is actually created at location which is obtained by the creation of a bytes array (https://eel.is/c++draft/intro.object#15).
我在构建同类型的连续对象,但由于数组具有隐式生命周期,一个由 double 构成的数组实际上是在 location 被创建的,而 location 是通过创建一个字节数组得到的(https://eel.is/c++draft/intro.object#15)。
解决方案
一种可理解的解释可能是,一旦 T* 是一个 T 对象的内存位置,它就始终是一个数组地址(https://eel.is/c++draft/dcl.array#6 和 https://eel.is/c++draft/expr.new#11)。
An object of type “array of N U” consists of a contiguously allocated non-empty set of N subobjects of type U, known as the elements of the array, and numbered 0 to N-1. The element numbered 0 is termed the first element of the array. 类型为“array of N U”的对象由一组连续分配的非空N 个U 类型的子对象组成,称为数组的元素,编号从0 到N-1。编号为0 的元素被称为数组的第一个元素。
and
When the allocated type is “array of N T” (that is, the noptr-new-declarator syntax is used or the new-type-id or type-id denotes an array type), the new-expression yields a prvalue of type “pointer to T” that points to the initial element (if any) of the array. Otherwise, let T be the allocated type; the new-expression is a prvalue of type “pointer to T” that points to the object created. 当分配的类型是“array of N T”(也就是使用noptr-new-declarator语法,或new-type-id或 type-id指定为数组类型)时,new表达式产生一个类型为“pointer to T”的prvalue,它指向数组的初始元素(如果有的话)。否则,设T 为分配的类型;new表达式是一个类型为“pointer to T”的prvalue,指向所创建的对象。
If there is a single T object, it is by all means equivalent to an array of size 1 (https://eel.is/c++draft/basic.compound#3).
如果只有一个 T 对象,那么它在任何情况下都等价于一个长度为1 的数组(https://eel.is/c++draft/basic.compound#3)。
...an object of type T that is not an array element is considered to belong to an array with one element of type T …一个类型为T 的对象若不是数组元素,则被视为属于一个只有一个元素的类型为T 的数组。
Then the size can be known only at runtime, the array could have been created implicitly, from a new expression or an array declaration, it does not matter. 因此,大小只能在运行时确定,数组可以通过隐式创建得到,可能来自new表达式或数组声明,这并不重要。
Thus when returning a T* from std::launder, and performing pointer arithmetic (https://eel.is/c++draft/expr.add#4), the compiler must assume that it is correct has soon as we point at most to the "one past end" element. Similarly, dereferencing an array element (either by *(ptr+i) or ptr[i]), the compiler must assume that there is a living T object there. If this assumptions are not true, the behavior is undefined.
因此,当从 std::launder 返回一个 T*,并进行指针运算(https://eel.is/c++draft/expr.add#4)时,编译器必须在我们最多指向“超出最后一个元素一个位置”时就假设这是正确的。类似地,解引用数组元素(通过 *(ptr+i) 或 ptr[i]),编译器必须假设那里存在一个存活的 T 对象。如果这些假设不成立,行为将是未定义的。
The point I am the most unsure of is that array can be implicitly created, while there objects are not (typically if they are not of implicit lifetime type): does this analysis hold when an array is in its lifetime but not its elements? should we interpret https://eel.is/c++draft/dcl.array#6 more liberally accepting such arrays? 我最不确定的一点是,数组可以被隐式创建,而其中的对象并非如此(通常如果它们不是隐式生命周期类型的话):当数组处于生命周期但其元素不在生命周期内时,这种分析是否仍然成立?我们是否应更宽松地解读 https://eel.is/c++draft/dcl.array#6,以接受这样的数组?