在 C++ 中调用析构函数
Invoking of destructors in C++
下面的一段代码有一个名为 String
的用户定义数据类型。这个 class 的对象存储一个字符指针 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
函数得到 invoked.Why 这是?
如果未定义析构函数,我将得到以下输出(如预期):
Well done!boy
Well done!
boy
- 为什么在定义析构函数时出现意外输出?
为此您需要复制构造函数:void add(String a,String b);
最好通过引用传递字符串:
void add(const String &a, const String &b);
同样在析构函数中,您需要使用 delete[] 释放内存。
delete[] str;
现在可以使用了。
这个默认构造函数
String(){}
不初始化任何东西,因为两个数据成员是基本类型,而基本类型不提供自动初始化。
因此,对结果实例的任何使用都将使用(数据成员的)不确定值,因此将具有未定义的行为。
修复:初始化数据成员。
您还需要负责复制和移动,例如避免双重 delete
s – 这也是未定义的行为。
示例代码在调用中复制 String
个实例
s1.add(s2,s3);
... 因为 add
按值获取参数。
修复:至少定义一个复制构造函数。
在析构函数中释放内存
delete str;
…不匹配str
的分配,它使用了new[]
。
它可以在实践中工作,但它是未定义的行为,因此会导致各种问题。
修复:对使用 new[]
创建的内容使用 delete[]
。
修复以上代码后仍会泄漏内存。一个简单的修复可能是定义一个连接构造函数,让 add
使用它来创建一个新的 String
实例,然后 swap
该实例和当前实例的状态。这种方法的优点是自动(好吧,几乎是自动)是异常安全的。
一般建议:
而不是 <cstring>
,只需使用 <string.h>
。一个优点是,与同名的 C 头文件一样,它保证将名称放在全局命名空间中。因此,如果您使用像 strcpy
这样的非限定名称,它将可以移植地工作,而不仅仅是手头编译器的偶然机会。
不是像void add(String a,String b)
那样按值传递潜在的大对象,而是按对[=27的引用传递它们=],如 void add(String const& a,String const& b)
,以避免不必要的复制。
最好不要在与 i/o 无关的 class 中执行 i/o,如 void display()
成员函数。例如,display()
函数在图形用户界面中不起作用。
下面的一段代码有一个名为 String
的用户定义数据类型。这个 class 的对象存储一个字符指针 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
函数得到 invoked.Why 这是?
如果未定义析构函数,我将得到以下输出(如预期):
Well done!boy
Well done!
boy
- 为什么在定义析构函数时出现意外输出?
为此您需要复制构造函数:void add(String a,String b);
最好通过引用传递字符串:
void add(const String &a, const String &b);
同样在析构函数中,您需要使用 delete[] 释放内存。
delete[] str;
现在可以使用了。
这个默认构造函数
String(){}
不初始化任何东西,因为两个数据成员是基本类型,而基本类型不提供自动初始化。
因此,对结果实例的任何使用都将使用(数据成员的)不确定值,因此将具有未定义的行为。
修复:初始化数据成员。
您还需要负责复制和移动,例如避免双重 delete
s – 这也是未定义的行为。
示例代码在调用中复制 String
个实例
s1.add(s2,s3);
... 因为 add
按值获取参数。
修复:至少定义一个复制构造函数。
在析构函数中释放内存
delete str;
…不匹配str
的分配,它使用了new[]
。
它可以在实践中工作,但它是未定义的行为,因此会导致各种问题。
修复:对使用 new[]
创建的内容使用 delete[]
。
修复以上代码后仍会泄漏内存。一个简单的修复可能是定义一个连接构造函数,让 add
使用它来创建一个新的 String
实例,然后 swap
该实例和当前实例的状态。这种方法的优点是自动(好吧,几乎是自动)是异常安全的。
一般建议:
而不是
<cstring>
,只需使用<string.h>
。一个优点是,与同名的 C 头文件一样,它保证将名称放在全局命名空间中。因此,如果您使用像strcpy
这样的非限定名称,它将可以移植地工作,而不仅仅是手头编译器的偶然机会。不是像
void add(String a,String b)
那样按值传递潜在的大对象,而是按对[=27的引用传递它们=],如void add(String const& a,String const& b)
,以避免不必要的复制。最好不要在与 i/o 无关的 class 中执行 i/o,如
void display()
成员函数。例如,display()
函数在图形用户界面中不起作用。