为什么在下面的代码中调用析构函数?

时间:2013-08-09 15:14:21

标签: c++

请考虑以下代码。此代码取自本书 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次? 这段代码错了吗?

5 个答案:

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