【问题标题】:push a 2D array into a vector and delete the array, but cause segmentation fault将二维数组推入向量并删除数组,但会导致分段错误
【发布时间】:2015-09-09 21:07:21
【问题描述】:

我有一个名为 Matrix 的类。类中有一个二维数组来保存数据。

    template <class Type>
    class Matrix{
        public:
           Matrix(int row, int col){
             rows = row; cols = col;
             data = new Type*[rows];
             for(int i = 0; i < rows; i++){
              data[i] = new Type[cols]; 
              }
           }
      public: 
         int rows;
         int cols;
         Type **data;
    };

我有一个向量来保存矩阵。每次我有一个矩阵时,我都会将这个向量中的矩阵推回以供将来计算。为了避免内存泄漏,我想在将 Matrix 推回向量后删除它。但是如果我不删除它,程序就可以工作;如果我删除它(如下面的代码所示),当我想对该向量进行一些计算时,程序将显示分段错误。我使用的代码如下所示

    vector<Matrix<int>> v; 
    for(int i = 0; i < 10; ++i){
         Matrix<int> a(3,3);
                 ...... // I fill the elements of a.data
       v.push_back(a);
       for(int j = 0; j < a.rows; ++j)
             delete[] a.data[j];
       delete[] a.data;
    }

希望我已经清楚地解释了我的问题。如果有什么让你感到困惑,请评论我。

谢谢你的帮助!!!

【问题讨论】:

  • 对于此类问题,使用valgrind 执行程序可能有助于揭示有关分段错误的详细信息以及其他内存泄漏。
  • 当您push_back 时,您创建了一个具有相同指针的副本(指针按值复制)。然后它指向同一个内存,然后您将其删除。您想创建一个为新矩阵分配内存的复制构造函数。
  • What is The Rule of Three? 的邪恶双胞胎。

标签: c++ class pointers matrix vector


【解决方案1】:

我在您的代码中发现了多个问题:

  • 它是 C++ 并且您正在为矩阵手动分配内存,为什么?
  • 即使您有权访问析构函数,您也不会实现它,而是在主代码中手动删除矩阵的数据
  • 您的方法没有清楚地管理内存,当您按值push_back 时,Matrix 被复制到向量内,此时谁拥有数据的指针?堆栈上的副本还是向量内的副本?

您应该通过为类实现正确的复制构造函数、复制赋值运算符和析构函数来小心管理内存。

但这无关紧要,因为您可以只使用 C++ 功能而忘记这些问题,一些解决方案:

在向量中存储指针

class Matrix{
public:
  Matrix(int row, int col){
    rows = row; cols = col;
    data = new Type*[rows];
    for(int i = 0; i < rows; i++){
      data[i] = new Type[cols];
    }
  }
  ~Matrix() {  // you need a destructor
    for (int i = 0; i < rows; ++i)
      delete[] data[i];
    delete data;
  }
public:
  int rows;
  int cols;
  Type **data;
};

std::vector<std::unique_ptr<Matrix>> v;
Matrix* matrix = new Matrix(3,3);
v.push_back(std::unique_ptr<Matrix>(matrix));

现在矩阵变得持久,但当v 超出范围时它们会自动释放(感谢unique_ptr 和析构函数)

对矩阵元素使用std::vector / std::array

您正在使用它们来存储多个矩阵,为什么不将它们也用于矩阵本身?

template<size_t ROWS, size_t COLS, class TYPE>
class Matrix
{
  std::array<std::array<COLS, TYPE>, ROWS> data;
  ...
}

现在一切都是自动管理的,你不需要释放内存,你根本不需要Matrix 的析构函数。

std::vector<Matrix<3,3,float>> v;
Matrix<3,3,float> m;
v.emplace_back(m);
m.data[0][0] = 1;

如果你想在同一个向量中有不同大小的矩阵,或者你想保持低堆栈使用率(因为std::array不是动态分配的)那么使用std::vector而不是std::array,这样你就可以删除模板参数。

【讨论】:

    【解决方案2】:

    您的Matrix 类没有正确的copy constructor,因此当您将新矩阵推送到vector 时,所有字段都将复制到vector 内新创建的Matrix

    AND Matrix::data 也被复制为指针。这意味着vector 中的新Matrix 指向相同 Matrix a,您在for 循环中创建。因此,当您删除a 时,实际上会使vector 中的Matrix 无效。

    【讨论】:

      【解决方案3】:

      OP 的问题是经典的Rule of Three 违规。

      类使用原始指针指向构造函数分配的内存

      Matrix(int row, int col)
      {
          rows = row; cols = col;
          data = new Type*[rows];
          for(int i = 0; i < rows; i++)
          {
              data[i] = new Type[cols]; 
          }
      }
      

      析构函数会删除所有内存,因此不会有泄漏。

      ~Matrix() 
      {  // you need a destructor
          for (int i = 0; i < rows; ++i)
              delete[] data[i];
          delete data;
      }
      

      但是,没有复制或移动构造函数,也没有赋值或移动操作符,因此默认情况下只会简单地复制指针,从而导致两个对象指向同一个内存。

      当一个被更改时,不仅两个副本都被修改,而且当一个被删除时,另一个副本的指针将变为无效。

      这通常被认为是不好的。

      解决方案一是创建复制和移动构造函数以及赋值和移动运算符,但这需要一些工作才能正确。

      谢天谢地,std::vector 开箱即用。

      template<class Type>
      class Matrix{
      public:
        Matrix(int row, int col):rows(row), cols(col), data(rows, std::vector(cols))
        {
          // does nothing. All of the heavy lifting was in the initializer
        }
        // no longer need a destructor.
      public:
        int rows;
        int cols;
        std::vector<std::vector<type>> data;
      };
      

      我建议的下一点是性能增强。因为向量的向量实际上是一个包含其他向量的向量,所以它并不都在一个内存块中。不得不在 RAM 中跳转以查找下一位数据的成本可能很高。查看Cache MissSpatial locality 了解原因。

      template<class Type>
      class Matrix{
      public:
        Matrix(int row, int col):rows(row), cols(col), data(rows*cols)
        {
          // does nothing. All of the heavy lifting was in the initializer
        }
        // no longer need a destructor.
        //add a convenience method for easy access to the vector
        type & operator()(size_t row, size_t col)
        {
          return data[row*cols+col];
        } 
        type operator()(size_t row, size_t col) const
        {
          return data[row*cols+col];
        } 
      private: // note change of access to private Best to keep ones data to one's self
        int rows;
        int cols;
        std::vector<type> data;
      };
      

      现在您可以安全地使用以下内容了:

      std::vector<Matrix<float>> v;
      Matrix<float> m(3,3);
      v.emplace_back(m);
      m(0,0) = 1;
      

      【讨论】:

        猜你喜欢
        • 2012-02-25
        • 2021-03-19
        • 1970-01-01
        • 1970-01-01
        • 2021-06-19
        • 2016-08-26
        • 2020-08-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多