【问题标题】:C++ Destructor being called in overloaded arithmetic operators在重载算术运算符中调用 C++ 析构函数
【发布时间】:2013-02-06 22:38:10
【问题描述】:

我有一个用于神经网络程序和重载算术运算符的定制矩阵库。 这是类声明:

class Matrix{
public:
int m;
int n;
double **mat;
Matrix(int,int);
Matrix(int);
Matrix(const Matrix& that):mat(that.mat),m(that.m),n(that.n)
    {
        mat = new double*[m];
        for(int i = 0;i<m;i++)mat[i] = new double[n];
    };
~Matrix();
friend istream& operator>>(istream &in, Matrix &c);
friend ostream& operator<<(ostream &out, Matrix &c);
Matrix operator+(const Matrix& other);
};

这是 + 操作的函数定义:

 Matrix Matrix::operator+(const Matrix& other)
    {
        Matrix c(m,n);
        for(int i=0;i<m;i++)
        {
           for(int j = 0; j<n;j++)
               c.mat[i][j] = mat[i][j] + other.mat[i][j];
        }
        return c;
    }

我已经尝试以各种方式实现它,但错误是一样的......这是一个实例

Matrix x(m,n); //m and n are known
x = a+b; // a and b are also m by n matrices

我已经使用断点调试了代码,这是错误... 算子函数中的局部矩阵'c'在返回之前被销毁,因此分配给x的是垃圾指针..

请给我一些建议...

【问题讨论】:

  • 当然是销毁了,c++就是这样工作的。您是否在复制构造函数和赋值运算符中实现了深层复制?并显示你的构造函数/析构函数。
  • 考虑使用vector,而不是使用double ** mat。这里的优点是您可以使用简单的复制构造函数。

标签: c++ matrix operator-overloading


【解决方案1】:

您需要为您的类定义一个复制构造函数。复制构造函数需要为mat 分配内存并复制数据。

没有这个,当你return c 时,会构造一个新对象,它的mat 值与c 相同。当c 随后超出范围时,它会删除c.mat。结果,c 的副本留下了一个悬空指针。

完成此操作后,您还应该实现一个赋值运算符。

【讨论】:

  • 感谢 NPE...我确实定义了一个复制构造函数!但是您能告诉我如何编辑我的操作员功能代码吗?提前致谢!
  • @Cheeku:如果你的构造函数(包括你的拷贝构造函数)和你的析构函数都正确实现了,我看不出你的操作符有什么问题,虽然它应该是一个 const 成员函数。
  • @BenjaminLindley 好吧,我确实使用复制构造函数修改了代码。我在我的问题中编辑了代码以显示这一点。但它没有按预期工作!可能的错误是什么?
【解决方案2】:

你返回的值被用来初始化一个临时值,然后这个临时值被复制到结果中你返回的值已经被销毁了。这是正常行为(除非调用因 NRVO 而被忽略)。

但是,由于您的类没有明确定义的复制构造函数,因此将调用隐式生成的复制构造函数,这只会将指针 (mat) 复制到已被返回对象的析构函数释放的内容。

这违反了所谓的 Rule of Three,这是一种编程最佳实践,即每当您的类显式定义复制构造函数、赋值运算符或析构函数时,它应该定义它们中的所有。基本原理是定义其中一个的类很可能这样做,因为它正在管理一些资源,并且为了正确处理资源释放/获取逻辑,需要所有这三个特殊成员函数。

请注意,在 C++11 中,您还可以有一个 move 构造函数,该构造函数将被允许执行Matrix 内容的传输,只需分配指针并使您移动的对象无效从。

Matrix(Matrix&& m)
{
    mat = m.mat;
    m.mat = nullptr;
}

当然,如果你引入了移动构造函数,你将不得不相应地修改你的类析构函数来检查你是否真的需要释放分配的内存:

~Matrix()
{
    if (m.mat == nullptr)
    {
        return;
    }

    ...
}

【讨论】:

  • 谢谢,复制构造函数是我不知道的!
【解决方案3】:

您的 Matrix 类有一个原始指针成员,并且可能在其构造函数中分配内存,但您没有复制构造函数或复制赋值运算符。

另外,你有一个析构函数,但你没有复制构造函数或复制赋值运算符。这违反了三法则。

【讨论】:

    【解决方案4】:

    您的Matrix c 是一个局部变量。因此,当创建它的方法结束时,它就会被销毁。在 C++ 中,这种不需要的情况通常通过复制对象来解决。您可以定义具有相同功能的复制构造函数和赋值运算符 =。复制的问题是它很慢,所以如果你想让它更快,你应该使用不同的方法而不是复制。例如,您可以在方法中添加一个参数,调用者将在该方法中传递对存储结果的现有矩阵对象的引用。

    【讨论】:

      【解决方案5】:

      您需要一个复制构造函数和一个赋值运算符为您的类制作对象的深层副本,因为编译器生成的函数不会。

      编译器生成的复制构造函数和赋值运算符将简单地复制包含在类中的对象。在您的情况下,这些是 POD,因此自动生成的函数将简单地进行按位复制。在double** 的情况下,这将导致指针值的副本,而不是指向的值。结果,在析构函数从你下面拉出地毯之前,你最终会得到两个指向相同底层数据的 Matrix 对象。

      【讨论】:

        【解决方案6】:

        您应该更改代码以返回 Matrix *,而不是 Matrix 对象。这样,您可以确保 Matrix 对象存在于函数之后。 (您当前的代码使 Matrix 对象成为函数变量,因此它将在函数结束后被删除。

        您的代码可能如下所示:

        Matrix *Matrix::operator+(const Matrix& other)
        {
            Matrix *c = new Matrix(m,n);
            for(int i=0;i<m;i++)
            {
               for(int j = 0; j<n;j++)
                   c->mat[i][j] = mat[i][j] + other.mat[i][j];
            }
            return c;
        }
        

        编辑:显然这是不好的做法,我猜我今天也学到了一些东西 :)

        【讨论】:

        • eww...恶心...请不要这样做。
        • 这种方法的问题和潜在问题是谁将销毁 c 对象以及我们如何确定它确实做到了。 (内存泄漏。)
        • 我会说你想要的是 + 运算符的新对象?那么创建一个新的有什么问题呢?
        猜你喜欢
        • 1970-01-01
        • 2017-03-12
        • 1970-01-01
        • 2020-03-10
        • 2012-04-01
        • 2011-02-23
        • 1970-01-01
        • 2011-10-11
        相关资源
        最近更新 更多