在堆上创建多维数组

3

经过一些实验,我总结出在堆上创建多维数组的四种方法(1和2有点类似,但是1的结果是我想要的引用):

#include <memory>
#include <iostream>

template <typename T>
void printArr(T const &arr)
{
    std::cout << typeid(T).name() << "\t";
    for (int x = 0; x < 2; ++x)
        for (int y = 0; y < 2; ++y)
            for (int z = 0; z < 2; ++z)
                std::cout << arr[x][y][z] << " ";
    std::cout << std::endl;
}


int main()
{
    int(&arr)[2][2][2] = reinterpret_cast<int(&)[2][2][2]>(*new int[2][2][2]{ { { 1,2 },{ 3,4 } }, { { 5,6 },{ 7,8 } } });
    printArr(arr);
    delete[] &arr;

    int(*arr2)[2][2] = new int[2][2][2]{ { { 1,2 },{ 3,4 } },{ { 5,6 },{ 7,8 } } };
    printArr(arr2);
    delete[] arr2;

    std::unique_ptr<int[][2][2]> arr3(new int[2][2][2]{ { { 1,2 },{ 3,4 } },{ { 5,6 },{ 7,8 } } });
    printArr(arr3);

    std::unique_ptr<int[][2][2]> arr4 = std::make_unique<int[][2][2]>(2);
    printArr(arr4);

    return 0;
}

在各种在线编译器上测试过,没有出现问题,所以我想知道这些方法是否也是有效的?这里有演示和输出: https://ideone.com/UWXOoW
int [2][2][2]   1 2 3 4 5 6 7 8
int (*)[2][2]   1 2 3 4 5 6 7 8
class std::unique_ptr<int [0][2][2],struct std::default_delete<int [0][2][2]> > 1 2 3 4 5 6 7 8
class std::unique_ptr<int [0][2][2],struct std::default_delete<int [0][2][2]> > 0 0 0 0 0 0 0 0

我认为第一个是错误的,其他三个是正确的。 - geza
@geza 嗯,是什么让你认为这是未定义行为呢? - Killzone Kid
2
嗯,为什么不直接使用 std::vector 呢? - Jesper Juhl
2
@JesperJuhl 太简单了))) 好吧,有些东西让我好奇,而且这也是一个很好的练习。 - Killzone Kid
通常情况下,您不应将引用用作任何东西的拥有副本。其他程序员期望引用指向某个存在于其他地方的东西,并且引用可以在不出问题的情况下超出范围。如果您需要在结束时删除某些内容,则应使用类似unique_ptr的东西,或者至少使用拥有指针而不是引用。 - Daniel H
1个回答

0

我认为你的第一个例子是未定义行为,或者至少在你尝试通过引用arr实际访问数组时会导致未定义行为。

new int[2][2][2]创建了一个包含两个int[2][2]的数组。它返回该数组的第一个元素的指针([expr.new] §1)。然而,指向数组的第一个元素的指针和指向数组本身的指针并不可互换。我不确定是否已经有一个明确的答案来回答哲学问题“解除无效指针的行为本身是否已经构成了未定义行为?”但至少从你的reinterpret_cast获得的引用访问应该明显违反了严格别名规则。其他三个应该没问题。

编辑:

由于仍然存在一些混淆,这里有一个更详细的解释:

new int[2][2][2]

创建一个元素类型为 int [2][2] 的数组,并返回该数组第一个元素的指针,即指向此数组中第一个子对象 int [2][2] 的指针,而不是整个数组对象本身。

如果你想从new获取一个 int(*)[2][2][2],例如:

new int[1][2][2][2]

1
std::launder 是否有效?那里有一个类型为 int[2][2][2] 的对象;只是您没有以有效的方式转换指针。 - Daniel H
如果您想动态创建一个int[2][2][2],可以使用new (int[2][2][2])。这就是我所做的,我首先用new创建了int[2][2][2],这就是为什么很难理解为什么将引用int(&)[2][2][2]指向它会导致未定义行为的原因。 - Killzone Kid
实际上,刚才我说错了。我错误地认为括号会改变什么。但实际上,无论你是使用 new int[2][2][2] 还是 new (int[2][2][2]),你都会得到一个 int(*)[2][2]。我已经从我的回答中删除了那部分内容。 - Michael Kenzel
@MichaelKenzel 不好意思挑刺,但实际的数组边界在最左边的 [] 中,而不是最右边的。 - HolyBlackCat
1
我应该注意到,正如 @DanielH 指出的那样,std::launder() 可能 是实现您想要以明确定义的方式执行的方法。但是我不100%肯定。 - Michael Kenzel
显示剩余16条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接