这可能只是我在某种程度上是愚蠢的,但我对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。这是为什么?
答案 0 :(得分:8)
你的签名错了:
Matrix operator+(Matrix a1, Matrix a2)
应该是
Matrix operator+(const Matrix& a1, const Matrix& a2)
它似乎会破坏a1
和a2
的原因是因为它们是,因为它们是在方法范围内创建的临时副本。
如果原始值被破坏,您可能违反了三条规则。当a1
和a2
被销毁时,析构函数被调用,你可能正在删除析构函数中的指针。但由于默认复制构造函数只执行浅复制,因此复制的a2
和a1
将删除原始内存。
假设:
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 x
和a1
上调用a2
,这将删除a.x
指向的内存和b.x
。这就是你得到内存损坏的原因。
但是,调用option 2
,因为通过引用传递没有创建新对象,所以在函数返回时不会删除内存。
但是,这不是解决问题的最简洁方法。它解决了这个问题,但潜在的问题更为重要,正如康拉德所指出的那样,而且我的原始答案(尽管没有给予它足够的重视,但我承认)。
现在,解决这个问题的正确方法是遵循三条规则。也就是说,实施destructor
,copy constructor
和assignment 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
因为副本a1
和a2
现在指向不同的内存位置,当它们被销毁时,原始内存完好无损且原始对象 - a
和b
仍然有效。
Phew!希望这可以解决问题。
答案 1 :(得分:6)
我假设你使用Matrix
在new
类中分配动态内存,但没有实现自定义拷贝构造函数,因此违反了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)
但即使是这个签名本身也不会破坏a
和b
。但是因为你在复制论点,我还有另一种怀疑。
您是否定义了自己的复制构造函数?我认为在将值复制到运算符的参数时,可能会删除旧变量。
请分享你的拷贝构造函数(最好也是operator =和你在这个运算符中使用的双参数构造函数)
答案 3 :(得分:1)
由于矩阵的大小是在运行时给出的,所以我认为Matrix类包含一个用动态分配的数组初始化的指针,该数组被析构函数删除。
在这种情况下,还必须定义复制构造函数和赋值运算符,以便分配矩阵所包含的副本。
不这样做,默认的复制和赋值只会重新分配指针,让你有两个或多个Matrix持有相同的数组。当一个被摧毁(从而删除阵列)时,其他人仍然悬空。