【问题标题】:Can we use conventional pointer arithmetic with std::array?我们可以在 std::array 中使用传统的指针算法吗?
【发布时间】:2019-02-06 18:32:13
【问题描述】:

我想研究如何在指向 std::array 类元素的指针上使用旧式指针算法。以下代码(可能不出所料)无法编译:

int main(int argc, char *argv[])
{
    double* data1 = new double[(int)std::pow(2,20)];
    std::cout << *data1 << " " << *(data1 +1) << std::endl;
    delete data1;
    data1 = NULL;

    double* data2 = new std::array<double, (int)std::pow(2,20)>;
    std::cout << *data2 << " " << *(data2 +1) << std::endl;
    delete data2;
    data2 = NULL;

    return 0;
}

作为练习,我想使用所有传统的指针算法,但不是指向旧式双精度数组,而是希望它指向 std::array 的元素。我对这一行的看法:

    double* data2 = new std::array<double, (int)std::pow(2,20)>;

是告诉编译器data2是指向分配的堆的第一个元素std::array&lt;double,(int)std::pow(2,20)&gt;的指针。

我被告知double* name = new double[size]; 的意思是完全如下:«堆栈分配内存用于指向 ONE double 的指针并将指针命名为 name,然后堆分配一个大小为size的双精度数组,然后将指针设置为指向数组的第一个元素。»由于上面的代码无法编译,我一定是被教导了一些不正确的东西,因为相同的语法不适用于 std::arrays。

这引发了几个问题:

  1. 声明type* name = new othertype[size];的实际含义是什么?
  2. 如何使用 std::array 实现我想要的?
  3. 最后,如何使用 std::unqiue_ptr 和 std::make_unique 实现相同的效果?

【问题讨论】:

  • new std::array&lt;double, 2&gt;; Nonononono,不要这样做——事实上,尽可能避免new!还有你到底需要什么指针算术? std::array 还为您提供通常优于原始指针的迭代器
  • 请问-为什么你想做原始指针算术?
  • 这应该是 std::array&lt;double, 2&gt; data2 以避免混乱、不必要的 new 和奇怪的转换为 double*。
  • (data1 +1)* ?你是说*(data1 +1) (data1[1]) 吗?

标签: c++ arrays c++11 c++-standard-library


【解决方案1】:

我被告知double* name = new double[size]; 的确切含义如下:«堆栈为指向 ONE double 的指针分配内存并命名指针名称,然后堆分配大小为大小的双精度数组,然后将指针设置为指向到数组的第一个元素。»由于上面的代码无法编译,我一定是被教导了一些不正确的东西,因为相同的语法不适用于 std::arrays。

您的说法是正确的,但请记住,它的工作方式是 new[] 与 new 是不同的运算符。当您动态分配std::array 时,您调用的是单对象new,返回的指针指向std::array 对象本身。

您可以对std::array 的内容进行指针运算。例如,data2.data() + 1 是指向data2[1] 的指针。请注意,您必须调用 .data() 来获取指向底层数组的指针。

无论如何,不​​要动态分配std::array 对象。尽可能避免动态分配,但如果需要,请使用std::vector。

【讨论】:

    【解决方案2】:

    我们可以对std::array 使用传统的指针算法吗?

    是的,当然可以 - 但不是在数组本身,它是一个对象。相反,您使用数组中数据的地址,通过std::array 的data() method 获得,如下所示:

    std::array<double, 2>  data2 { 12.3, 45.6 };
    double* raw_data2 = data2.data(); // or &(*data2.begin());
    std::cout << *raw_data2 << " " << *(raw_data2 + 1) << std::endl;
    

    还有这个compiles and runs fine。但是你可能并不真的需要使用指针算法,并且可以使用更好的 std::array 抽象来编写不同的代码。

    PS - 避免对new 和delete 使用显式内存分配(请参阅C++ Core Guidelines item 关于此问题)。在您的情况下,您根本不需要堆分配 - 就像您不需要常规数组一样。

    【讨论】:

      【解决方案3】:

      您可以使用data() 成员函数访问std::array 的“原始指针”视图。但是,std::array 的重点是您不必这样做:

      int main(int argc, char *argv[])
      {
          std::array<double, 2> myArray;
          double* data = myArray.data();
          // Note that the builtin a[b] operator is exactly the same as
          // doing *(a+b).
          // But you can just use the overloaded operator[] of std::array.
          // All of these thus print the same thing:
          std::cout << *(data) << " " << *(data+1) << std::endl;
          std::cout << data[0] << " " << data[1] << std::endl;
          std::cout << myArray[0] << " " << myArray[1] << std::endl;
      
          return 0;
      }
      

      【讨论】:

        【解决方案4】:

        广义的含义:

        type* name = new othertype[size];
        

        最终是“我需要一个变量name,它是一个指向type 的指针,并使用new[] 使用othertype 的size 实例的连续分配来初始化它”。请注意,这涉及强制转换,甚至可能无法正常工作,因为 othertype 和 type 可能不支持该操作。 double 的 std::array 不等同于指向 double 的指针。它是一个指向std::array 句点的指针,但如果您想假装它是double,并且您不介意您的程序是否由于未定义的行为而崩溃,您可以继续。你的编译器应该在这里警告你,如果没有,你的警告不够严格。

        标准库容器都是关于迭代器,而不是指针,尤其不是指针算法。迭代器比指针更灵活、更强大,它们可以处理链表、树等奇异的数据结构,而不会给调用者带来很多开销。

        像std::vector 和std::array 这样的一些容器支持“随机访问迭代器”,这是一种直接指针式访问其内容的形式:a[1] 等等。仔细阅读任何给定容器的文档,因为有些允许这样做,而许多不允许这样做。

        请记住,“变量”和“在堆栈上分配”不一定是同一回事。优化编译器可以并且会将该指针放在任何它想要的地方,包括寄存器而不是内存,或者如果它认为可以在不破坏代码表达行为的情况下摆脱它,则根本不放在任何地方。

        如果你想要std::array,你真的会这样做,因为标准库容器几乎总是比 C 风格的数组好:

        std::array<double, 2> data2;
        

        如果您需要共享此结构,则需要考虑使用std::unique_ptr 的费用是否值得。这个东西的内存占用会很小,复制它也很简单,所以使用相对昂贵的内存管理功能是没有意义的。

        如果您要传递一个更大的结构,请考虑使用引用,并将该结构定位在您拥有的最中心数据结构中,这样就不需要按设计复制。

        【讨论】:

          【解决方案5】:

          当然,这些都是合法的:

          template<class T, std::size_t N>
          T* alloc_array_as_ptr() {
            auto* arr = new std::array<T,N>;
            if (!arr) return nullptr;
            return arr->data();
          }
          template<class T, std::size_t N>
          T* placement_array_as_ptr( void* ptr ) {
            auto* arr = ::new(ptr) std::array<T,N>;
            return arr->data();
          }
          template<std::size_t N, class T>
          std::array<T, N>* ptr_as_array( T* in ) {
            if (!in) return nullptr;
            return reinterpret_cast<std::array<T,N>*>(in); // legal if created with either above 2 functions!
          }
          // does not delete!
          template<std::size_t N, class T>
          void destroy_array_as_ptr( T* t ) {
            if (!t) return;
            ptr_as_array<N>(t)->~std::array<T,N>();
          }
          // deletes
          template<std::size_t N, class T>
          void delete_array_as_ptr(T* t) {
            delete ptr_as_array<N>(t);
          }
          

          令人震惊的是,如果使用得当,上述内容实际上是合法的。指向数组第一个元素的指针是可与整个 std::array 相互转换的指针。

          您必须自己跟踪数组大小。

          我不建议这样做。

          【讨论】:

          • 虽然这一切都是真的,而且你告诉 OP 不要使用它,但警告在答案的末尾和“egads,谁教你这样做?”代码在开头...
          【解决方案6】:

          std::array毕竟是一个STL容器!

          auto storage = std::array<double, 1 << 20>{};
          auto data = storage.begin();
          std::cout << *data << " " << *(data + 1) << std::endl;
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-06-12
            • 2011-04-05
            • 1970-01-01
            • 2017-01-12
            • 2015-04-24
            • 2019-02-16
            • 1970-01-01
            相关资源
            最近更新 更多