将二维数组推入矢量并删除数组,但会导致分割错误

push a 2D array into a vector and delete the array, but cause segmentation fault

本文关键字:错误 分割 数组 删除 二维数组      更新时间:2023-10-16

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

    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;
    };

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

    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;
    }

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

谢谢你的帮助!

我看到你的代码中有多个问题:

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

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

但这是无关的,因为你可以只使用c++的特性,忘记这些问题,一些解决方案:

在vector

中存储指针
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,这样你就可以删除模板参数。

您的Matrix类没有适当的copy constructor,所以当您将新矩阵推到vector时,所有字段都复制到vector内新创建的Matrix

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

OP的问题违反了经典的"三原则"

类使用原始指针指向构造函数

分配的内存
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 Miss和Spatial 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;