逐个元素创建的数组的生命周期

编程语言 2026-07-10

这只是一个纯理论性的语言法理问题,因为它所描述的情景没有实际用途(这会是一个真的糟糕的设计)。

场景是在一个非字节类型的数组中创建存储,然后把与之无关的类型的对象连续放入其中:

#include <cstddef>
#include <memory>
#include <new>

// unrelated types
using store_t = double;
using data_t = short;

// some data_t object generator
template <typename T>
data_t make_data(T val) {
    return data_t{static_cast<data_t>(val)};
}

int main() {
    // creating a storage from store_t (not providing storage, not implicitly
    // creating objects)
    constexpr std::size_t N = 100;
    auto* Storage = new store_t[N];

    // retrieving a suitably aligned pointer in storage to hold a data_t object
    std::size_t sz = sizeof(store_t) * N;
    void* ptr = static_cast<void*>(Storage);
    if (nullptr == std::align(alignof(data_t), sizeof(store_t) * N, ptr, sz)) {
        throw std::bad_alloc();
    }

    // inplace construction of contiguous data_t object inside the original
    // storage
    // ends the array of store_t lifetime
    constexpr std::size_t n = 10;
    for (std::size_t i = 0; i < n; ++i) {
        std::construct_at(reinterpret_cast<data_t*>(ptr) + i, make_data(i));
    }
    // is there a data_t array in its lifetime at ptr?
    ...
}

根据 https://eel.is/c++draft/dcl.array#6

类型为 “N个 U的数组” 的对象由一个连续分配的非空的N 个U 型子对象集合组成,这些子对象被称为数组元素,编号从0 到N-1。编号为0 的元素被称为数组的第一个元素。

因此我理论上构造了一个具有 数组对象 布局的东西,可以与指针运算一起使用(例如,https://eel.is/c++draft/basic.compound#3):

    data_t const* const array = std::launder(
        reinterpret_cast<data_t*>(ptr));  // is this a living array?

    for (std::size_t i = 0; i < n; ++i) {
        std::cout << array[i];  // is this legal?
    }

但据我所知,这个数组既没有显式也没有隐式地开始它的生存期。

operator new[] 隐式创建对象,而数组具有隐式生存期类型,但我不确定我们能否因为两个原因而依赖隐式对象创建。

首先,https://eel.is/c++draft/intro.object#14https://eel.is/c++draft/intro.object#16。如果我对这些小节的理解正确,隐式创建对象的地址应当是 operator new[] 返回的那个地址,但由于对齐调整,尤其是对于非新对齐类型,情况可能并非如此。

此外,data_t 数组并未嵌套在 store_t 数组之内,因为后者不提供存储。因此我们将会有两个互不相关的对象共享同一个位置,这属于未定义行为(严格别名规则违规:https://eel.is/c++draft/basic.lval#11)。

那么,data_t 的数组在其生存期内吗,为什么?


这是完整示例:

#include <cstddef>
#include <iostream>
#include <memory>
#include <new>

// unrelated types
using store_t = double;
using data_t = short;

// some data_t object generator
template <typename T>
data_t make_data(T val) {
    return data_t{static_cast<data_t>(val)};
}

int main() {
    // creating a storage from store_t (not providing storage, not implicitly
    // creating objects)
    constexpr std::size_t N = 100;
    auto* Storage = new store_t[N];

    // retrieving a suitably aligned pointer in storage to hold a data_t object
    std::size_t sz = sizeof(store_t) * N;
    void* ptr = static_cast<void*>(Storage);
    if (nullptr == std::align(alignof(data_t), sizeof(store_t) * N, ptr, sz)) {
        throw std::bad_alloc();
    }

    // inplace construction of contiguous data_t object inside the original
    // storage
    // ends the array of store_t lifetime
    constexpr std::size_t n = 10;
    for (std::size_t i = 0; i < n; ++i) {
        std::construct_at(reinterpret_cast<data_t*>(ptr) + i, make_data(i));
    }
    // is there a data_t array in its lifetime at ptr?

    data_t const* const array = std::launder(
        reinterpret_cast<data_t*>(ptr));  // is this a living array?

    for (std::size_t i = 0; i < n; ++i) {
        std::cout << array[i];  // is this legal?
    }

    // cleanly releasing resources
    for (std::size_t i = 0; i < n; ++i) {
        std::destroy_at(std::launder(reinterpret_cast<data_t*>(ptr) + i));
    }


    // releasing storage
    operator delete[](Storage);
}

LIVE

解决方案

用来为 Storage 指向的存储分配的 operator new[] 会隐式创建对象,但由于随后 new 运算符在那里创建了一个 store_t 的数组,因此没有这样的对象能存活到对 reinterpret_cast<data_t*>(ptr) 的指针运算。没有数组对象,这种运算将是未定义的。你可以尝试对数组底层的字节进行指针运算,但标准在这一点上仍不明确(尽管修订正在进行中,in progress),并且得到的对象也不会成为一个数组的成员(除非显式创建一个数组),即使它们具有相邻的地址。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章