【问题标题】:Copy constructor memory leaks复制构造函数内存泄漏
【发布时间】:2021-06-17 09:36:57
【问题描述】:

我正在尝试学习编写自定义构造函数的基础知识,但我无法弄清楚我做错了什么。我知道就我的目的而言,让编译器完成它的工作就足够了,但我很好奇如何修复我的定义。

#include <iostream>
#include <stdexcept>

class Matrix {
    public:
      Matrix(int rows, int cols);    //my custom constructor
     ~Matrix();                      //my custom destructor
      Matrix(const Matrix& m);       //my custom copy constructor
      Matrix& operator= (const Matrix& m);  //my custom assignment operator
    private:
      int rows_, cols_;
      double* data_;
    };


Matrix::Matrix(int rows, int cols): rows_ (rows), cols_ (cols){  
if (rows == 0 || cols == 0)
    throw std::out_of_range("Matrix constructor has 0 size");
data_ = new double[rows * cols];     
}
 
 
Matrix::~Matrix()       
{
    delete[] data_;
}


Matrix::Matrix(const Matrix& m) : rows_(m.rows_), cols_(m.cols_)
{
data_ = new double[rows_ * cols_];
data_=m.data_;
} 


Matrix& Matrix::operator=(const Matrix& m){     
if(this != &m){
        double* newdata_=new double[m.cols_*m.rows_];
        *newdata_=*data_;
        delete[] data_;
        data_=newdata_;
        rows_=m.rows_;
        cols_=m.cols_;
        }   
return *this;

}
    

然后在程序的主体部分:

int main(){

Matrix m1(2,2);//creating a matrix of size 2x2


Matrix m2=m1;  //this doesn't work
Matrix m3(m1); //nor this




return 0;
}

运行可执行文件时的错误是: free(): 在 tcache 2 中检测到双重空闲

我认为复制构造函数和赋值运算符都不会导致调用析构函数是否正确?这是为什么呢?

【问题讨论】:

  • *data_ = *m.data_; 只会复制数组第一个元素的值,对吧?这实际上是仅让编译器完成其工作是不够的,因为它不会执行data_的深层复制。
  • 但基本上至少存在三个问题:data_ = m.data_; 复制的是指针,而不是数组的值。 *newdata_ = *data_; 也没有对数组进行完整的复制。在一些地方,您实际上不会从m 复制值:您的复制构造函数没有在LHS 中设置rows_cols_,并且您永远不会在赋值运算符中访问m.data_。所以你会发生内存泄漏和双重删除,因为你实际上并没有复制数组。
  • data_ = new double[rows_ * cols_]; data_=m.data_; -- 你在连续的行中执行此操作。您已经在第一行中分配了数据,但是第二行通过再次更改 data_ 来清除所有这些数据。所以很明显这是错误的。
  • 复制构造函数中的data_=m.data_; 行是您代码中实际拥有的吗?与您的副本分配的*newdata_=*data_; 相比,它看起来有点不一致。 (虽然它设法避免了双重释放,但并不是说复制分配正确地做事......)你希望这行代码完成什么?

标签: c++ constructor destructor copy-constructor


【解决方案1】:

基本问题是在复制构造函数中分配内存后,您并没有复制 数据

Matrix::Matrix(const Matrix& m) : rows_(m.rows_), cols_(m.cols_)
{
    data_ = new double[rows_ * cols_];
    data_ = m.data_;  // <-- This is wrong
} 

带有// &lt;-- This is wrong 注释的行不仅擦除了前一行(分配内存的位置),它不会复制任何实际数据。它所做的只是复制指针值。所以你现在有 data_m.data_ 指向同一个内存,因此内存泄漏和双重删除错误。

解决方法是将数据实际复制到新分配的内存中。

另一个不容易发现的潜在错误是您未能初始化所有数据。即使我们修复了此问题以进行复制,您也会遇到未定义的行为。

以下是解决这两个问题的方法:

#include <algorithm>
//...
Matrix::Matrix(int rows, int cols): rows_ (rows), cols_ (cols)
{  
    if (rows == 0 || cols == 0)
        throw std::out_of_range("Matrix constructor has 0 size");
    data_ = new double[rows * cols](); // <-- Note the () to zero-initialize the data    
}

Matrix::Matrix(const Matrix& m) : rows_(m.rows_), cols_(m.cols_)
{
    data_ = new double[rows_ * cols_];
    std::copy_n(m.data_, m.rows_ * m.cols_, data_);
} 

还有一个错误,那就是赋值运算符。您犯了同样的错误,错误地使用= 进行复制,而不是在两个缓冲区之间复制数据的必要函数。

Matrix& Matrix::operator=(const Matrix& m)
{     
    if(this != &m)
    {
        double* newdata_=new double[m.cols_*m.rows_];
        *newdata_=*data_; // <-- This does not copy the data
        //

修复类似于在复制构造函数中使用std::copy_n 完成的修复。但它是如此相似,以至于您实际上可以使用复制构造函数来完成所有这些工作,而不是使用重复的代码。这种在赋值运算符中使用复制构造函数的技术称为copy / swap idiom

Matrix& Matrix::operator=(const Matrix& m)
{     
  if(this != &m)
  {
     Matrix temp(m);  // <-- Copy is made
     std::swap(temp.data_, data_);
     std::swap(temp.rows_, rows_);
     std::swap(temp.cols_, cols_);
  }      
  return *this;
}

基本上制作了一个副本,然后我们只需将当前对象的数据替换为副本的数据。然后副本与旧数据一起消失。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-12
    • 2019-08-19
    • 1970-01-01
    • 1970-01-01
    • 2018-11-18
    • 1970-01-01
    相关资源
    最近更新 更多