这种错误可能就是与内存有关的释放问题。这里的错误示例代码主要是为了说明复制构造函数,尤其是含有指针类型或者有成员表示在构造函数中分配的其他资源的情况下应该应当被正确处理。
错误示例代码:
#include <iostream>
#include <cstring>
using std::cout;
using std::endl;
//复制构造函数举例1
//此例包含指针成员,没有复制构造函数出错
struct Node
{
Node(char *n="",int a = 0)
{
name = strdup(n);
strcpy(name,n);
age = a ;
}
~Node()
{
delete[] name;
}
char *name;
int age;
};
int main()
{
Node node1("Roger",20),node2(node1);
//print Roger 20 Roger 20
cout<<node1.name<<" "<<node1.age<<" "
<<node2.name<<" "<<node2.age<<endl;
strcpy(node2.name,"Wendy");
node2.age = 30;
//print Wendy 20 Wendy 30
cout<<node1.name<<" "<<node1.age<<" "
<<node2.name<<" "<<node2.age<<endl;
}
注意,这里的strdup函数是C语言中函数,它会根据串长用malloc分配内存的,返回分配的内存首地址。
这段程序执行时输出:
Roger 20 Roger 20
Wendy 20 Wendy 30
并产生错误:
0x77D9FCAA (ntdll.dll) (prog31.exe 中)处有未经处理的异常: 0xC0000374: 堆已损坏。 (参数: 0x77DC6668)。
解决办法:包含指针类型或者构造函数中包含资源分配的类,需要定义自己的复制构造函数而不是依赖编译器合成的复制构造函数。
这里依赖编译器合成的复制构造函数,从node1构造node2时,node2.name指针进行简单的重定向,定向到node1.name所指向的字符串,因此二者共享同一份字符串地址,因此再执行strcpy(node2.name,"Wendy");出现了数据不一致行的错误,两者的name全部都是Wendy,而年龄更新却是正确的。同样,由于共享同一份字符串地址,在析构函数中释放同一份内存两次,导致堆已损坏的错误。当然,如果在析构函数中删除name数组空间后,将name指针置为空,不会产生堆损坏错误。但是在析构函数中将指针置为空,将隐藏程序bug,与复制构造函数相关的错误依然存在。
因此解决的办法,就是正确定义Node类如下:
#include <iostream>
#include <cstring>
using std::cout;
using std::endl;
//复制构造函数举例2
struct Node
{
Node(char *n="",int a = 0)
{
name = strdup(n);
strcpy(name,n);
age = a ;
}
//复制构造函数
Node(const Node& node)
{
name = strdup(node.name);
age = node.age;
}
//赋值操作符
Node& operator=(const Node& n)
{
if(this != &n)
{
if(name != NULL)
delete [] name;//释放先前空间
name = strdup(n.name);//重新分配内存
age = n.age;
}
return *this;
}
//析构函数
~Node()
{
delete[] name;
}
char *name;
int age;
};
int main()
{
Node node1("Roger",20),node2(node1),node3("Tom",22);
//print Roger 20 Roger 20
cout<<node1.name<<" "<<node1.age<<" "
<<node2.name<<" "<<node2.age<<endl;
strcpy(node2.name,"Wendy");
node2.age = 30;
//print Roger 20 Wendy 30
cout<<node1.name<<" "<<node1.age<<" "
<<node2.name<<" "<<node2.age<<endl;
//赋值操作符
node2 = node3;
//print Tom 22 Tom 22
cout<<node2.name<<" "<<node2.age<<" "
<<node3.name<<" "<<node3.age<<endl;
return 0;
}
这里因为可以由三法则(Rule of Three)即一个类如果需要析构函数,则该类几乎也必然需要定义自己的复制构造函数和赋值操作符解释。重新运行程序,即可得到正确结果并避免堆损坏错误。