【问题标题】:Invoking of destructors in C++在 C++ 中调用析构函数
【发布时间】:2016-10-28 10:33:07
【问题描述】:

以下代码有一个名为String的用户定义数据类型。此类的对象存储一个字符指针str(字符串的简写)和length

#include<iostream>
#include<cstring>
using namespace std;
class String
{
    char* str;
    int length;
public:
    String(){}                     //default constructor 
    String(const char* s)
    {
        length=strlen(s);
        str=new char[length+1];   
        strcpy(str,s);
    }
    void add(String a,String b)   //function to concatenate strings
    {
        length=a.length+b.length;
        str=new char[length+1];
        strcpy(str,a.str);
        strcat(str,b.str);
    }
    void display()
    {
        cout<<str<<endl;
    }
    ~String()                                //destructor 
    {
        delete str;
        cout<<"Destructor invoked!";
    }
};
int main()
{
    String s1;
    String s2("Well done!");
    String s3("Boy");
    s1.add(s2,s3);
    s1.display();
    s2.display();
    s3.display();
}

输出:

Destructor invoked!Destructor invoked!Well done!boy
X!!;
<!!;
Destructor invoked!Destructor invoked!Destructor invoked!
  • 看起来好像在调用析构函数之前 display 函数被调用。为什么会这样?

如果没有定义析构函数,我会得到以下输出(如预期):

Well done!boy
Well done!
boy 
  • 为什么在定义析构函数时会出现这种意外输出?

【问题讨论】:

  • 析构函数被调用是因为add的参数被销毁了。
  • 而且您的班级不符合 Rule-of-3。您至少需要添加复制构造函数和复制赋值运算符。
  • 你有两个内存泄漏双重删除。查看addwhat is the rule of three?
  • delete str 应该是 delete[] str; - 你在 add 中也有内存泄漏
  • 还需要在默认构造函数中初始化成员。

标签: c++ destructor


【解决方案1】:

为此,您需要复制构造函数:void add(String a,String b); 最好通过引用传递字符串:

void add(const String &a, const String &b);

同样在析构函数中,你需要使用 delete[] 来释放内存。

delete[] str;

现在它可以工作了。

【讨论】:

  • Re“现在可以了。”,不,只是添加了复制构造函数后代码仍然有UB。
【解决方案2】:

这个默认构造函数

String(){} 

不初始化任何东西,因为这两个数据成员是基本类型,一个基本类型不提供自动初始化。

因此,对结果实例的任何使用都将使用(数据成员的)不确定值,因此将具有未定义的行为。

修复:初始化数据成员。


您还需要负责复制和移动,例如避免双重deletes - 这也是未定义的行为。

示例代码在调用中复制String实例

s1.add(s2,s3);

...因为add 按值获取参数。

修复:至少定义一个复制构造函数。


在析构函数中释放

delete str;

…与str的分配不匹配,后者使用了new[]

它可以在实践中工作,但它是未定义的行为,因此会导致各种问题。

修复:将delete[] 用于使用new[] 创建的内容。


修复上述问题后,代码仍会内存泄漏。一个简单的解决方法是定义一个连接构造函数,让add 使用它来创建一个新的String 实例,然后swap 该实例和当前实例的状态。这种方法具有自动(嗯,几乎是自动)异常安全的优点。


一般建议:

  • 不要使用&lt;cstring&gt;,而是使用&lt;string.h&gt;。一个优点是,与同名的 C 标头一样,它保证将名称放在全局命名空间中。因此,如果您使用像 strcpy 这样的非限定名称,它将可以移植,而不仅仅是手头的编译器的巧合。

  • 不要像void add(String a,String b) 那样按值 传递潜在的大对象,而是像void add(String const&amp; a,String const&amp; b) 那样通过const 的引用来传递它们,避免不必要的复制。

  • 最好不要在与 i/o 无关的类中执行 i/o,例如 void display() 成员函数。例如,display() 函数在图形用户界面中不起作用。

【讨论】:

    猜你喜欢
    • 2014-05-03
    • 1970-01-01
    • 2020-04-10
    • 2018-06-02
    • 1970-01-01
    • 2021-07-19
    • 2017-04-28
    • 2015-08-13
    • 2019-11-09
    相关资源
    最近更新 更多