如何创建一个数组并开始其生命周期而又不开始其任何元素的生命周期? [英] How to create an array and start its lifetime without starting the lifetime of any of its elements?

查看:75
本文介绍了如何创建一个数组并开始其生命周期而又不开始其任何元素的生命周期?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

任何类型的数组都是隐式生命周期对象,并且可以从隐式生命周期对象的生命周期开始,而无需开始子对象的生存期.

Arrays of any type are implicit-lifetime objects, and it is possible to to begin the lifetime of implicit-lifetime object, without beginning the lifetime of its subobjects.

据我所知,创建隐式生命周期对象的动机之一是创建数组而不以不导致UB的方式开始其元素生命周期的可能性,请参阅

As far as I am aware, the possibility to create arrays without beginning the lifetime of their elements in a way that doesn't result in UB, was one of the motivations for implicit-lifetime objects, see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html.

现在,正确的做法是什么?分配内存并返回指向数组的指针就足够了吗?还是还有其他需要注意的事情?

Now, what is the proper way to do it? Is allocating memory and returning a pointer to array is enough? Or there is something else one needs to be aware of?

就是说,此代码有效吗,它是否创建具有未初始化成员的数组,还是我们仍然拥有UB?

Namely, is this code valid and does it create an array with uninitialized members, or we still have 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] 的数组,并启动其寿命,但不会开始其任何元素的寿命.

According to https://en.cppreference.com/w/cpp/memory/allocator/allocate, the allocate function function creates an array of type T[n] in the storage and starts its lifetime, but does not start lifetime of any of its elements.

一个简单的问题-如何完成?(忽略 constexpr 部分,但是我不介意是否在答案中也解释了 constexpr 部分).

A simple question - how is it done? (ignoring the constexpr part, but I wouldn't mind if constexpr part is explained in the answer as well).

PS:所提供的代码对c ++ 20有效(假定正确),但据我所知,对于早期标准无效.

PS: The provided code is valid (assuming it is correct) for c++20, but not for earlier standards as far as I am aware.

我相信,对此问题的回答也应回答我之前提出的两个类似问题.

I believe that an answer to this question should answer two similar questions I have asked earlier as well.

  1. 数组和隐式生命周期对象创作.
  2. 是否有可能以一种方式分配未初始化的数组不会导致UB .

我要添加一些代码片段,以使我的问题更加清楚.我希望能得到一个答案来解释哪个有效,哪些无效.

I am adding few code snippets, to make my question more clear. I would appreciate an answer explaining which one are valid and which ones are not.

PS:随时用对齐的版本或 :: operator new 变体替换 malloc .据我所知,这没关系.

PS: feel free to replace malloc with aligned version, or ::operator new variation. As far as I am aware it doesn't matter.

示例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相同,但数组大小已知.

Example #3 - same as 2 but size of array is known.

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(*)[] .动态构造数组.另外, Nicol Bolas的答案是我问题的正确答案.

While wording of the standard is not 100% clear, after reading the paper more carefully, the motivation is to make casts to T* legal and not casts to T(*)[]. Dynamic construction of arrays. Also, the changes to the standard by the authors of the paper imply that the cast should be to T* and not to T(*)[]. Hence, the accepting the answer by Nicol Bolas as the correct answer for my question.

推荐答案

隐式对象创建的全部要点是隐式.就是说,您什么也没做就可以实现.一旦IOC出现在一块内存上,您就可以像使用存在问题的对象一样使用内存,只要您这样做,代码就可以正常工作.

The whole point of implicit object creation is that it is implicit. That is, you don't do anything to get it to happen. Once IOC occurs on a piece of memory, you may use the memory as if the object in question exists, and so long as you do that, your code works.

当您从 allocator_traits<> :: allocate 取回 T * 时,如果将1加到指针,则该函数已返回一个数组at至少1个元素(新指针可以是数组的过去指针).如果再次加1,则该函数已返回至少包含2个元素的数组.等等,这都不是未定义的行为.

When you get your T* back from allocator_traits<>::allocate, if you add 1 to the pointer, then the function has returned an array of at least 1 element (the new pointer could be the past-the-end pointer for the array). If you add 1 again, then the function has returned an array of at least 2 elements. Etc. None of this is undefined behavior.

如果您执行与此操作不一致的操作(广播到其他指针类型,并像在那里有一个数组一样工作),或者如果您好像该数组超出了IOC应用于的存储空间的大小,则您得到UB.

If you do something inconsistent with this (casting to a different pointer type and acting as though there is an array there), or if you act as though the array extends beyond the size of the storage that IOC applies to, then you get UB.

因此,只要分配器隐式分配的内存创建对象, allocator_traits :: allocate 并不需要做任何事情.

So allocator_traits::allocate doesn't really have to do anything, so long as the memory that the allocator allocated implicitly creates objects.

// 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 notconstructed, hence UB

都不是.它返回一个指针(键入 T )以存储可能已经隐式创建对象的对象.哪些对象是隐式创建的,具体取决于您使用此存储的方式.并且仅进行演员表并不构成使用"动作.存储空间.

Neither. It returns a pointer (to type T) to storage into which objects may have been implicitly created already. Which objects have been implicitly created depends on how you use this storage. And merely doing a cast doesn't constitute "using" the storage.

不是 reinterpret_cast 会导致UB;这是 使用由错误的 reinterpret_cast 返回的指针 .而且由于IOC基于会导致UB的操作进行工作,所以IOC不在乎将指针投射到什么地方.

It isn't the reinterpret_cast that causes UB; it's using the pointer returned by an improper reinterpret_cast that's the problem. And since IOC works based on the operation that would have caused UB, IOC doesn't care what you cast the pointer to.

IOC规则的重要组成部分是推论" 适用创建的对象"规则.该规则表示某些操作(例如 malloc 新操作员 )返回指向合适的创建对象"的指针.从本质上讲,它可以返回到量子叠加:如果IOC追溯创建一个对象以使您的代码起作用,则这些函数会追溯返回一个指针,该指针指向创建的使您的代码起作用的任何对象.

Part and parcel of the IOC rules is the corollary "suitable created object" rule. This rule says that certain operations (like malloc and operator new) return a pointer to a "suitable created object". Essentially it's back to quantum superposition: if IOC retroactively creates an object to make your code work, then these functions retroactively returns a pointer to whichever object that was created that makes your code work.

因此,如果您的代码将指针用作 T * 并对该指针执行指针算术,则 malloc 返回一个指针,该指针指向 T s.这个数组有多大?那要看:分配有多大,指针算法进行了多长时间?里面有实时的 T 吗?这取决于:您是否尝试访问数组中的任何 T ?

So if your code uses the pointer as a T* and does pointer arithmetic on that pointer, then malloc returned a pointer to the first element of an array of Ts. How big is that array? That depends: how big was the allocation, and how far did you do your pointer arithmetic? Does it have live Ts in them? That depends: do you try to access any Ts in the array?

这篇关于如何创建一个数组并开始其生命周期而又不开始其任何元素的生命周期?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆