为什么在以下代码中调用析构函数

Why does destructor get called in the following code?

本文关键字:调用 析构函数 代码 为什么      更新时间:2023-10-16

请考虑以下代码。此代码取自书中使用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[] ( 应添加到析构函数中。
  • 由于此类动态地将内存分配给成员变量,因此默认复制构造函数和默认赋值运算符是不够的,因为它们只执行成员变量的浅拷贝,而不执行深层拷贝。鉴于您提供的代码(我上面提到的两个更改(,不会调用此行为,但请记住这一点,以便将来使用。