我只是想创建一个简单的程序来练习一些C ++,但我不确定为什么我得到这个当前的错误。输出提供了我想要的结果,但在成功输出后,我不断收到调试断言错误。它是内存泄漏还是什么?我不知道它可能是什么。
部首:
#include <iostream>
class Record {
char* rec;
public:
Record();
Record(const char*);
Record(const Record&);
~Record();
void display(std::ostream&);
Record& operator=(const char*);
};
std::ostream& operator<<(std::ostream& os, Record& r);
CPP:
#define _CRT_SECURE_NO_WARNINGS
#include "Record.h"
Record::Record() {
rec = nullptr;
}
Record::Record(const char* s) {
if(s != nullptr) {
rec = new char[strlen(s) + 1];
strcpy(rec, s);
} else {
*this = Record();
}
}
Record::Record(const Record& r) {
*this = r;
}
Record::~Record() {
delete [] rec;
}
void Record::display(std::ostream& os) {
os << rec;
}
Record& Record::operator=(const char* s) {
if (rec != s)
delete [] rec;
if(s != nullptr) {
rec = new char[strlen(s) + 1];
strcpy(rec, s);
}
else {
rec = nullptr;
}
return *this;
}
std::ostream& operator<<(std::ostream& os, Record& r) {
r.display(os);
return os;
}
主:
#include <iostream>
#include "Record.h"
using namespace std;
int main() {
Record rec1("inheritance"), rec2 = rec1;
cout << rec1 << endl;
cout << rec2 << endl;
rec1 = "overloading";
cout << rec1 << endl;
rec2 = rec1;
cout << rec2 << endl;
return 0;
}
答案 0 :(得分:1)
我会把它作为一个答案,因为它对你的班级表现如何很重要,并且结论是“一切正常”#34;是C ++不是那么容易用语言的原因之一。
你编写的main()程序没有测试非常简单的东西。看这里:
int main() {
Record rec1("inheritance");
Record rec2 = rec1;
}
如果您调试此代码,您将看到为rec2 = rec1行调用此函数:
Record::Record(const Record& r) {
*this = r;
}
好的,所以调用了复制构造函数。但是这行代码做了什么?:
*this = r;
它不调用您编写的带有const char *的赋值运算符。相反,它调用默认的赋值运算符来记录&amp;,问题是 - 你没有写一个。所以最后发生的事情是编译器生成的赋值运算符被调用,它执行浅拷贝。
在main()程序中,当main()返回时,rec2和rec1都将调用它们各自的析构函数。问题是rec2将删除指针值,ok,但是rec1将删除相同的指针值(没有好处),导致堆损坏。我使用Visual Studio 2013运行代码,并在main()返回时立即弹出一个断言对话框。
因此,您需要编写一个带有此签名的用户定义赋值运算符:
Record& Record::operator=(const Record&)
答案 1 :(得分:0)
不要从*this =
和复制构造函数调用赋值运算符const char*
。这通常是不好的做法。在您的情况下,因为您还没有定义一个带const Record&
的赋值运算符,所以会调用默认赋值运算符,它只是复制指针并执行浅复制,意味着两个Record
将在rec
s中具有相同的指针 - 这在PaulMcKenzie's answer中已经指出并详细描述。但是,即使您确实定义了赋值运算符,如果您执行与现有赋值运算符相同的操作,则成员变量rec
将未初始化,delete
会导致未定义的行为。
请参阅Calling assignment operator in copy constructor进行讨论,以及您可以做什么,而不是从构造函数中调用赋值运算符。
修改的
使程序运行的一种方法是让构造函数看起来像这样。
void Record::InitFrom(const char* s)
{
if(s != nullptr) {
rec = new char[strlen(s) + 1];
strcpy(rec, s);
} else {
rec = nullptr;
}
}
Record::Record(const char* s) {
InitFrom(s);
}
Record::Record(const Record& r) {
InitFrom(r.s);
}
您还可以将新的InitFrom
方法合并到赋值运算符中。
Record& Record::operator=(const char* s) {
if (rec != s) { // this test only really necessary if assigning from Record
delete [] rec;
InitFrom(s);
}
return *this;
}
您也可能拥有一个const Record&
的赋值运算符。你做需要析构函数。
答案 2 :(得分:0)
如果没有看到您初始化记录的值,很难确定是什么。根据您对程序行为和代码的描述,我可以想到一个可能的解释,尽管可能还有其他的。
我的猜测是std :: ostream将char *像c字符串一样处理,它需要一个以\ 0结尾的字符序列。如果你的Record已经用一系列没有以\ 0结尾的字符进行初始化,那么它将继续进行指针递增,一次流出一个字符,直到它到达无效的内存部分。这将导致未定义的行为,这可能会在您的标准实现(即您正在使用的编译器)中触发调试断言。
我向你提供这个猜测,因为你说这是一个学习exersize,所以我不会质疑你的课堂设计,但试着帮助你了解发生了什么。然而,其他地方的评论非常相关。如果您有记录存储std :: string而不是字符数组,则不会发生此问题(以及其他一些可能的问题)。当然,这个答案可能无法帮助你学习你想要学习的知识。