请考虑以下代码。此代码取自本书 Object Oriented Programming With C++! - 第12章模板。
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
class vc
{
int size2;
int *v;
public :
vc(int size1);
vc(int *a);
~vc()
{
printf("\n calling destructor");
}
int operator *(vc );
};
vc::vc(int size1)
{
v = new int[size2=size1];
for(int i=0;i<this->size2;i++)
v[i]=0;
}
vc::vc(int a[])
{
for(int i=0;i<this->size2;i++)
{
v[i]=a[i];
}
}
int vc::operator *(vc v1)
{
int total=0;
for(int i=0;i<size2;i++)
total+=(v[i]*v1.v[i]);
return total;
}
int main()
{
int a[3]={1,2,3};
int b[3]= {5,6,7};
vc v1(3),v2(3);
v1=a;
v2=b;
int total = v1*v2;
cout << total;
return 0;
}
首先,此代码无法正常运行。它应该显示38作为输出。当我开始调试此代码时,我发现在此行3
之后size2
已分配给vc v1(3),v2(3);
。但是在执行下一行时,控制权将传递给第二个构造函数,size2
显示垃圾值。此外,析构函数在行v1=a
之后调用,同样在下一行之后调用。
calling destructor
calling destructor
calling destructor0
为什么析构函数会被调用3次? 这段代码错了吗?
答案 0 :(得分:8)
当您致电v1*v2
时,您会将v2
传递给方法
int vc::operator *(vc v1)
创建v2
的本地副本。因此,您还有一个vc
实例。
你第一个疑问的答案,
但是在执行下一行时,控制权将传递给第二行 constructor和size2显示一个垃圾值,并且它不会执行三次。
是因为
v1 = a;
通过调用vc
创建一个临时vc::vc(int a[])
并将其分配给v1
。但是,此构造函数未初始化size2
。所以你得到了一个垃圾值。
更简洁的方法是传递数组及其大小:
vc::vc(int a[], int size1) {
v = new int[size2=size1];
for(int i=0; i<size2; i++)
v[i] = a[i];
}
int main()
{
int a[3]={1,2,3};
int b[3]= {5,6,7};
vc v1(a, 3), v2(b, 3);
int total = v1*v2;
cout << total;
return 0;
}
然后total
将是38。
答案 1 :(得分:5)
v1=a;
这条线实际上不仅仅是一项任务。它是临时对象的构造,并将其分配给已创建的对象。将int[]
分配给编译器可以看到的类的唯一方法是使用vc(int a[])
构造函数创建vc
的临时对象 - 但该构造函数不初始化size2,这就是你所看到的问题(“但是在执行下一行时,控件被传递给第二个构造函数,size2显示垃圾值。”)。
创建此临时值后,隐式赋值运算符(由编译器自动为您创建,因为您未指定)将将此对象的成员复制到已创建的对象。
这称为隐式转换(参见例如:https://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8l.doc%2Flanguage%2Fref%2Fcplr384.htm)。
如果您想阻止这种情况发生,请使用explicit
构造函数上的vc(int a[])
关键字。
事后的想法:我最初的回答并没有真正回答最后提出的主要问题(为什么析构函数被调用三次)。我想其他人已经很好地回答了这个问题:在vc
内部创建了一个operator*
对象,因为参数传递的值是你在那里做的。
现在你可以问为什么析构函数实际上没有被调用5次(隐式创建的对象也是2次)。我想这是因为编译器以某种方式优化了新对象的实际创建(可能是Return Value Optimization,如果我错了,请有人纠正我!)。
答案 2 :(得分:3)
v1*v2
创建第三个临时对象,因此也会调用它的析构函数以及您创建的两个对象。
答案 3 :(得分:2)
你应该通过引用而不是值来传递额外析构函数的原因。
int vc::operator *(vc &v1)
而且,从现场回答nyarlathotep。除了添加一个显式关键字,这将阻止该错误。
你只是错过了这个
void vc::operator=(int* a)
{
for(int i=0;i<this->size2;i++)
{
v[i]=a[i];
}
}
答案 4 :(得分:1)
正如其他人所说的那样,析构函数被调用三次是因为当调用operator*(vc v1)
时,会构造一个临时本地副本,因为它接受值<{1}}的参数
nyarlathotep给出的答案是 正确。 innosam的建议几乎是理想的。
我想补充一些其他更具体的要点。
有两点需要注意:
v1
方法一样,始终传递通过引用到const:operator*
operator*(const vc& v1)
仅调用构造函数v1 = a
,因为整数数组vc(int a[])
隐式转换为a
的实例化,如nyarlathotep所述。您可以通过向此构造函数添加关键字vc
来避免这种情况,该构造函数指示编译器不允许此类转换。保持explicit
中的示例相同,产生您正在寻找的答案的代码更改次数最少(38)如下:
main
以采用operator*
类型的参数。const vc&
。它的实现可以与构造函数vc& operator=(int src[])
完全相同。您会注意到,这两个更改将不再调用构造函数vc(int a[])
。
此外,虽然我知道这只是一本书中的一个例子(并且回应nyarlathotep,希望它是不要做的事情的一个例子),这个类还有其他问题我想指出其中几个:
vc(int a[])
)添加到析构函数中。