【问题标题】:Creating multidimensional array on the heap在堆上创建多维数组
【发布时间】:2019-01-05 12:38:30
【问题描述】:

经过一些实验,我想出了这四种在堆上创建多维数组的方法(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

【问题讨论】:

  • 我觉得第一个是UB,其他三个都可以。
  • @geza 嗯,是什么让你认为它是 UB?
  • 嗯。为什么不直接使用std::vector
  • @JesperJuhl 太容易了)))好吧,有些东西让我很好奇,而且这是一个很好的练习
  • 一般情况下,您不应将引用用作任何内容的拥有副本。其他程序员希望引用是指存在于其他地方的东西,并且引用可以超出范围而不会出现问题。如果最后需要删除某些内容,则应使用unique_ptr 之类的内容,或者至少使用拥有指针而不是引用。

标签: c++ multidimensional-array c++14 language-lawyer heap-memory


【解决方案1】:

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

new int[2][2][2] 创建一个包含两个 int[2][2] 的数组。它会返回一个指向该数组第一个元素的指针 ([expr.new] §1)。但是,指向数组第一个元素的指针和指向数组本身的指针不是pointer interconvertible。我不确定哲学问题是否有明确的答案“取消引用无效指针的行为本身是否已经构成未定义的行为?”。但至少访问从您的 reinterpret_cast 获得的参考肯定会违反strict aliasing rule。其他三个应该没问题。

编辑:

由于似乎仍有一些混乱,这里对我的论点进行更详细的解释:

new int[2][2][2]

创建一个包含两个int[2][2] 的数组,并返回一个指向该数组第一个元素的指针,即指向第一个int[2][2] 子对象的指针 这个数组中,不是完整的数组对象本身

如果你想从new 得到一个int(*)[2][2][2],你可以,例如,做

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

【讨论】:

  • 我认为指针互转换在这里不适用,因为我正在通过引用 An lvalue expression of type T1 can be converted to reference to another type T2. The result is an lvalue or xvalue referring to the same object as the original lvalue, but with a different type. No temporary is created, no copy is made, no constructors or conversion functions are called. The resulting reference can only be accessed safely if allowed by the type aliasing rules 重新解释左值。不过,类型别名规则可能是问题所在。
  • 我相信这是来自旧 C++03 标准的措辞!?至少据我了解,目前关于此的措辞是[expr.reinterpret.cast] §11,它通过指针转换定义了引用的reinterpret_cast。不管怎样,我认为别名规则肯定是个问题。
  • 我发布了一个替代答案,感谢您的链接,它表明它不是UB,请检查
  • std::launder 是否有效?那里有一个int[2][2][2] 类型的对象;只是你没有以有效的方式转换指针。
  • 我应该注意到,正如@DanielH 指出的那样,std::launder() 可能是一种以明确定义的方式实际执行您想做的事情的方式。但我不是 100% 确定。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-20
  • 2010-09-25
  • 2011-08-19
  • 2013-04-21
相关资源
最近更新 更多