【问题标题】:c++11 using std::fill to fill a 2D dynamically allocated arrayc++11 使用 std::fill 填充二维动态分配的数组
【发布时间】:2016-04-01 06:11:09
【问题描述】:

可以在 questionhere 中找到有关这方面的讨论,但我的情况略有不同,因为我正在处理动态分配的内存。

另请注意,memsetdouble 值不完全匹配。

无论如何,我正在尝试使用std::fill 来填充动态分配的二维数组--

#include <iostream>
#include <algorithm>

using std::cout ; using std::endl ;
using std::fill ;

int main()
{
    double **data ;
    int row = 10, col = 10 ;
    data = new double*[row] ;
    for(int i = 0 ; i < col ; i++)
        data[i] = new double[col];

    // fill(&(data[0][0]), 
    //      &(data[0][0]) + (row * col * sizeof(double)), 1); // approach 1
    // fill(&(data[0][0]), &(data[0][0]) + (row * col), 1);   // approach 2
    // fill(data, data + (row * col * sizeof(double)), 1);    // approach 3
    // fill(&(data[0][0]), 
    //      &(data[0][0]) + 
    //       ((row * sizeof(double*)) + 
    //        (col * sizeof(double))), 1);                    // approach 4

    for(int i = 0 ; i < row ; i++) {
        for(int j = 0 ; j < col ; j++)
            cout << data[i][j] << " " ;
        cout << endl ;
    }

    for(int i = 0 ; i < row ; i++)
        delete [] data[i] ;
    delete [] data ;
}

方法一:据我了解,方法一应该是正确的代码,我是从开头&amp;(data[0][0])开始,数组的结尾应该位于&amp;(data[0][0]) + (row * col * sizeof(double)),但是当我运行时,我得到了这个错误,但是数组已经被填充了--

1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
*** Error in `./test': free(): invalid next size (fast): 0x0000000000da3070 ***
Aborted (core dumped)

Approach 2: 但是,根据这个post,建议使用方法2,但是我不太明白这段代码,因为缺少sizeof(double),我得到了这个输出--

1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 
1 1 1 1 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
*** Error in `./test': free(): invalid next size (fast): 0x0000000000bf5070 ***
Aborted (core dumped)

方法3:不知道为什么这样编译不通,data&amp;(data[0][0])应该是同一个意思吧?

方法4:我不确定这是否正确。

  1. 我该怎么做?
  2. std::fill 是否比两个嵌套循环有任何额外的好处?

【问题讨论】:

    标签: c++ arrays c++11 dynamic-memory-allocation


    【解决方案1】:

    与堆栈分配的二维数组不同,动态二维数组不能保证是一个连续的范围。然而,它是一个连续的指针范围,但数组中的每个指针都可能指向不连续的内存区域。换句话说,data + i + 1 的第一个元素不一定跟在data + i 指向的数组的最后一个元素之后。如果您想知道为什么堆栈分配的二维数组是连续的,那是因为当您声明类似

    double data[10][20];
    

    然后编译器将其理解为由 10 个(连续)元素组成的数组,每个元素的类型为 double[20]。后一种类型也是一个数组,它保证了连续的元素,因此double[20]元素(即20个double一个接一个)在内存中一个接一个地堆叠。 double[10][20]double** 截然不同。

    这就是为什么std::fillstd::memset 让您头疼的原因,因为它们都假定一个连续的范围。因此,在您的情况下,嵌套循环似乎是填充数组的最简单方法。

    一般来说,使用“模仿”二维访问的一维数组要好得多,这正是上述原因:数据局部性。数据局部性意味着更少的缓存丢失和更好的整体性能。

    【讨论】:

      【解决方案2】:

      指针算术要求指针仅在结果仍指向同一数组(或最后一个)的范围内递增。

      您在 for 循环中将每一行分配为一个单独的数组:

      for(int i = 0 ; i < col ; i++)
          data[i] = new double[col]; // this creates a distinct array for each row
      

      由于您分配的每个行数组都是col 元素,因此可以合法添加到&amp;(data[0][0]) 的最大值是col。但是在您使用std::fill 的每个示例中,您向指针添加的内容超出了您的允许范围。

      鉴于您分配数组的方式,您无法将原始指针传递给对std::fill 的单个调用以初始化整个二维数组。要么你必须多次调用std::fill(这违背了使用std::fill的目的),要么你必须创建一个知道如何处理每一行的单独分配的Iterator类型,或者你必须改变方式你分配数组。

      我建议将整个数组一次性分配为一维数组,然后编写一些额外的代码使其像二维数组一样工作。这有很多好处:

      • 标准库包含动态分配一维数组的便捷方式:std::vector
      • 使用std::vector 意味着您不再需要使用裸newdelete,这解决了您的代码存在的异常安全问题。
      • 单个分配通常比多次分配具有更好的性能特征(当然,在某些情况下单独分配更好)。

      这是一个简单的包装类,可以让一维数组看起来像一个二维数组:

      class Matrix {
        std::vector<double> data;
        int cols;
      
      public:
        Matrix(int row, int col)
          : data(row * col)
          , cols(col)
        {}
      
        auto begin() { return data.begin(); }
        auto end() { return data.end(); }
      
        struct index_type { int row; int col; };
      
        double &operator[](index_type i) {
          return data[i.row * cols + i.col];
        }
      
        int row_count() const { return data.size()/cols; }
        int column_count() const { return cols; }
      };
      

      使用它你可以重写你的代码:

      #include "Matrix.h"
      
      #include <iostream>
      #include <algorithm>
      
      using std::cout ; using std::endl ;
      using std::fill ;
      
      int main()
      {
          Matrix data(10, 10);
      
          fill(data.begin(), data.end(), 1.0);
      
          for(int i = 0 ; i < data.row_count() ; i++) {
              for(int j = 0 ; j < data.column_count() ; j++)
                  cout << data[{i, j}] << " " ;
              cout << endl ;
          }
      }
      

      std::fill 是否比两个嵌套循环有任何额外的好处?

      使用循环的可读性较差,因为循环可以做很多其他事情,而且您必须花更多时间弄清楚任何特定循环在做什么。出于这个原因,人们应该总是更喜欢使用 STL 算法而不是手动循环,其他条件都相同。


      // fill(&(data[0][0]), 
      //      &(data[0][0]) + (row * col * sizeof(double)), 1); // approach 1
      

      指针算法会自动考虑数组元素的大小。你不需要sizeof(double)。这里乘以sizeof(double) 与在[] 中乘以sizeof(double) 相同。你不会这样做:data[i * sizeof(double)],所以不要这样做data + (i * sizeof(double))


      您的示例代码使用&amp;(data[0][0])。想想这是否与data[0] 相同或不同。考虑表达式的类型和值。

      【讨论】:

      • 赞成。我也会写 operator[] 的 'const' 版本。使用自定义索引而不是 std::pair 或两个 size_t 参数的任何特殊原因?
      • @vsoftco 我删除了const 成员只是因为这里碰巧不需要他们,而且我想让代码保持简单。但是,是的,在真实代码中它们应该存在(以及一堆其他东西)。
      • @vsoftco 我为自定义成员名称(而不是.first.second)使用了自定义索引类型,而且因为这种方式静态类型可以防止混淆Matrix::index_typeSomethingElse::index_type。有时过多地使用像std::pair 这样的泛型类型会导致“字符串类型”问题。使用两个索引参数需要我摆脱[] 重载,这很好,我只是碰巧不想这样做。
      • 很公平,谢谢!仍然认为应该在标准中提出一个基本的矩阵类。 C++ 可能是唯一缺少它的主要语言。当然还有 Boost 多数组,但仍然没有进入标准。
      • @vsoftco 我认为array_view proposal 涵盖了大部分需要的内容。
      【解决方案3】:

      我同意上述cmets。您已经分配了 10 个单独的数组,因此您无法使用单个 std::fill 调用来初始化它们。 此外,当您对非 void 类型的指针执行算术运算时,编译器会自动将您的结果乘以给定类型的 sizeof。但是,当您使用 memset 或 memcpy 之类的函数时,您实际上必须将元素数量乘以给定类型的 sizeof 并将其传递给这些函数之一。这是因为这些函数对字节进行操作,并且它们接受 void 类型的指针。因此编译器不可能负责调整大小,因为 void 类型没有指定大小。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-08-07
        • 2019-02-26
        • 2021-02-03
        • 2015-02-10
        • 2023-03-14
        • 2015-09-17
        • 1970-01-01
        相关资源
        最近更新 更多