C ++运算符会破坏输入变量

时间:2012-03-20 13:23:06

标签: c++ operators

这可能只是我在某种程度上是愚蠢的,但我对C ++相对较新,所以原谅我白痴。我正在努力教自己如何使用运算符。我已经定义了一个非常基本的运算符如下:

Matrix::Matrix operator+(Matrix a1, Matrix a2) {
    if(a1.rows != a2.rows || a1.cols != a2.cols) {
        std::cout << "ERROR: Attempting to add matrices of non-equal size." << std::endl;
        exit(6);
    }

    int i, j;

    Matrix c(a1.rows, a1.cols);

    for(i = 0; i < a1.rows; i++)
        for(j = 0; j < a1.cols; j++)
            c.val[i][j] = a1.val[i][j] + a2.val[i][j];

    return c;
}

Matrix类表示一个矩阵,并且有一个构造函数,它以两个整数作为输入(分别是矩阵中的行数和列数),并创建一个具有适当大小的双精度数组(名为val)。此函数的作用应该是因为c的值是正确的,但它似乎也会破坏a1和a2。也就是说,如果我写

Matrix c = a + b;

它给出了正确的结果,但是a和b不再可用,并且在代码结束时我得到一个glibc错误,声称我试图在它们已经被破坏时破坏a和b。这是为什么?

4 个答案:

答案 0 :(得分:8)

你的签名错了:

Matrix operator+(Matrix a1, Matrix a2)

应该是

Matrix operator+(const Matrix& a1, const Matrix& a2)

它似乎会破坏a1a2的原因是因为它们是,因为它们是在方法范围内创建的临时副本。

如果原始值被破坏,您可能违反了三条规则。当a1a2被销毁时,析构函数被调用,你可能正在删除析构函数中的指针。但由于默认复制构造函数只执行浅复制,因此复制的a2a1将删除原始内存。

编辑:由于对此存在分歧意见,我将扩展我的答案:

假设:

struct A
{
   int* x;
   A() { x = new int; *x = 1; }
   ~A() { delete x; }
};

//option 1:
A operator + (A a1, A a2)
{
   A a; return a; //whatever, we don't care about the return value
}

//option 2:
A operator + (const A& a1, const A& a2)
{
   A a; return a; //again, we don't really care about the return value
}

在第一个示例中,未实现复制构造函数。

编译器生成复制构造函数。此复制构造函数将x复制到新实例中。所以如果你有:

A a;
A b = a;
assert( a.x == b.x );

重要请注意指针是相同的。

调用option 1将在operator +内创建副本,因为值是按值传递的:

A a;
A b;
a + b; 
//will call:
A operator + (A a1, A a2)
// a1.x == a.x
// a2.x == n.x

operator +退出时,它会在对象delete xa1上调用a2,这将删除a.x指向的内存和b.x。这就是你得到内存损坏的原因。

但是,调用option 2,因为通过引用传递没有创建新对象,所以在函数返回时不会删除内存。

但是,这不是解决问题的最简洁方法。它解决了这个问题,但潜在的问题更为重要,正如康拉德所指出的那样,而且我的原始答案(尽管没有给予它足够的重视,但我承认)。

现在,解决这个问题的正确方法是遵循三条规则。也就是说,实施destructorcopy constructorassignment operator

struct A
{
   int* x;
   A() { x = new int; *x = 1; }
   A(const A& other)  //copy constructor
   {
      x = new int;  // this.x now points to a different memory location than other.x
      *x = other.(*x);  //copy the value though
   }
   A& operator = (const A& other) //assignment operator
   { 
      delete x; //clear the previous memory
      x = new int;     
      *x = other.(*x);  //same as before
   }
   ~A() { delete x; }
};

使用这个新代码,让我们重新运行之前有问题的option 1

A a;
A b;
a + b; 
//will call:
A operator + (A a1, A a2)
// a1.x != a.x
// a2.x != n.x

因为副本a1a2现在指向不同的内存位置,当它们被销毁时,原始内存完好无损且原始对象 - ab仍然有效。

Phew!希望这可以解决问题。

答案 1 :(得分:6)

我假设你使用Matrixnew类中分配动态内存,但没有实现自定义拷贝构造函数,因此违反了rule of three

错误的原因将是Matrix实例的本地副本重用参数实例的动态内存,并且它们的析构函数在方法结束时释放它。

解决方案非常简单:Don’t use pointers。相反,您可以使用嵌套的std::vector来存储数据。

此外,运营商的负责人受损。根据您声明函数的位置,如果您在类中定义运算符,它必须看起来像这样:

ReturnType operator +(Arg1)

或者,如果你在课外定义它,它需要看起来像这样:

ReturnType operator +(Arg1, Arg2)

你们两者都是混合的。我假设您的运算符定义应如下所示:

Matrix operator +(Matrix a, Matrix b) { … }

如果您的代码编译,那么这可能是编译器中的错误,或者您确实使用了非常奇怪的类结构。此外,正如Luchian指出的那样,将参数传递为const&而不是通过值更有效。但是,您应该首先正确运行代码。

答案 2 :(得分:2)

正如Luchian Grigore指出的那样,你的签名是错误的,应该是:

Matrix operator+(const Matrix& a1, const Matrix& a2)

但即使是这个签名本身也不会破坏ab。但是因为你在复制论点,我还有另一种怀疑。

您是否定义了自己的复制构造函数?我认为在将值复制到运算符的参数时,可能会删除旧变量。

请分享你的拷贝构造函数(最好也是operator =和你在这个运算符中使用的双参数构造函数)

答案 3 :(得分:1)

由于矩阵的大小是在运行时给出的,所以我认为Matrix类包含一个用动态分配的数组初始化的指针,该数组被析构函数删除。

在这种情况下,还必须定义复制构造函数和赋值运算符,以便分配矩阵所包含的副本。

不这样做,默认的复制和赋值只会重新分配指针,让你有两个或多个Matrix持有相同的数组。当一个被摧毁(从而删除阵列)时,其他人仍然悬空。