为什么在以下代码中调用析构函数
Why does destructor get called in the following code?
请考虑以下代码。此代码取自书中使用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 次?这段代码有错吗?
当你调用v1*v2
时,你把v2
传递给方法
int vc::operator *(vc v1)
这将创建v2
的本地副本。因此,您有一个额外的vc
实例。
你第一个疑问的答案,
但是在执行下一行时,控制权被传递给第二行 构造函数和 size2 显示垃圾值,并且三次未执行。
是因为
v1 = a;
通过调用 vc::vc(int a[])
创建临时vc
并将其分配给 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 岁。
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(。如果要防止这种情况发生,请在vc(int a[])
构造函数上使用 explicit
关键字。
事后思考:我最初的回答并没有真正回答最后陈述的主要问题(为什么析构函数被调用三次(。我想这里的其他人已经很好地回答了这一点:由于您在那里执行的值按参数传递,因此在您的operator*
内部本地创建了一个vc
对象。
现在你可以问为什么析构函数实际上没有被调用 5 次(对于隐式创建的对象也是 2 次(。我想这是因为编译器以某种方式优化了新对象的实际创建(可能是返回值优化,如果我错了,有人请纠正我!
v1*v2
创建第三个临时对象,因此也称为其析构函数以及您创建的两个对象。
你应该通过引用而不是值传递,这就是额外析构函数的原因。
int vc::operator *(vc &v1)
而且,从尼亚拉霍特普的回答现场。除了添加一个明确的关键字,这可以防止错误。
你只是错过了这个
void vc::operator=(int* a)
{
for(int i=0;i<this->size2;i++)
{
v[i]=a[i];
}
}
正如其他人所说,析构函数被调用三次,因为当调用operator*(vc v1)
时,会构造一个临时的本地副本,因为它接受按值v1
的参数。
尼亚拉霍特普给出的答案是正确。因诺萨姆的建议几乎是理想的。
我想补充一些更具体的观点。
需要注意的两件事:
- 当传递只想在被调用函数内部引用的对象实例(而不是复制或更改(时,就像这个
operator*
方法一样,始终通过引用 const:operator*(const vc& v1)
-
v1 = a
确实调用构造函数vc(int a[])
,只是因为整数数组a
隐式转换为vc
的实例化,如 nyarlathotep 所述。可以通过向此构造函数添加关键字explicit
来避免这种情况,该构造函数指示编译器不允许此类转换。
保持示例main
相同,生成您要查找的答案的代码更改次数最少 (38( 如下:
- 更改
operator*
以采用类型const vc&
的参数。 - 定义自定义赋值运算符:
vc& operator=(int src[])
。它的实现可以与构造函数vc(int a[])
完全相同。
您会注意到,构造函数vc(int a[])
将不再通过这两项更改进行调用。
此外,虽然我知道这只是一本书中的一个例子(为了呼应nyarlathotep,希望它是一个不该做的事情的例子(,但这个类还有其他问题,我想指出其中的几个:
- 内存清理(即
delete[]
( 应添加到析构函数中。 - 由于此类动态地将内存分配给成员变量,因此默认复制构造函数和默认赋值运算符是不够的,因为它们只执行成员变量的浅拷贝,而不执行深层拷贝。鉴于您提供的代码(我上面提到的两个更改(,不会调用此行为,但请记住这一点,以便将来使用。
- 什么时候调用析构函数
- C++-明确何时以及如何调用析构函数
- C++ 防止在映射中放置()时调用析构函数
- 调用析构函数以释放动态分配的内存
- C++:使用方法调用析构函数的顺序是什么?
- 向量推回调用析构函数时调用析构函数
- 如何在调用析构函数时优雅地停止/销毁带有阻塞调用C++线程?
- C++,我应该调用析构函数吗?
- 如何获取有关在 Clang LibTooling 中调用析构函数的信息?
- 当我从 std::vector 中的新放置调用析构函数时会发生什么?
- 为什么这里不调用析构函数
- 在调用 std::bind 的产品后意外调用析构函数
- 为什么在传递给函数而不是构造函数时调用析构函数?
- 如何在C++中调用析构函数
- 为什么为未删除的对象调用析构函数?
- 调用析构函数时出错
- C++ 在不释放内存的情况下调用析构函数
- 为什么在运算符删除中不调用析构函数?
- C++ 调用析构函数后动态模板队列"double free or corruption (out)"
- 在 postOrderDelete 上调用析构函数时引发的异常