我正在清理我使用XCode 5为类编写的玩具程序。部分任务是熟悉动态内存分配(意味着必须使用new
和{ {1}}尽管如果没有它,它确实可行。)我收到了:
delete
发生错误:
HeapOfStudents(67683,0x7fff769a6310) malloc: *** error for object 0x100300060: pointer being freed was not allocated *** set a breakpoint in malloc_error_break to debug
被称为......
Student::~Student() {
delete firstName; // <- on this line
delete lastName;
delete address;
delete birthDate;
delete gradDate;
delete gpa;
delete crHours;
}
完整的int main() {
string line = "";
vector<Student> students;
//
//Create new students from file
//
ifstream data_file ("heapData");
if (data_file.is_open()) {
while(getline(data_file, line))
students.push_back(*new Student(line)); <- inside this call here
data_file.close();
}
else return 0;
课程如下。
重要的是,我注意到,当每个Student
被正确推送到向量时,当下一个line
被推送时,之前的Student
内部向量被腐败了。我总是在循环的第三次迭代中看到Student
错误(文件有50行)。
如果我将malloc
更改为vector<Student>
而将vector<Student *>
更改为student.push_back(*new Student(line))
,则该程序将毫无例外地运行。
我的问题是:有人可以解释一下,在这个循环中发生了什么,在较低的层次上发生了这个错误?
我的猜测是student.push_back(new Student(line))
每次都使用相同的指针,因此在每次迭代中,数据都会被释放,因为它被释放(尽管这种解释会引发问题),而*new Student(line)
会返回一个新的指针每次都保留传递给new Student
的数据。我想我可能需要编写一个复制构造函数......这只是我的猜测。
这是students
课程。 Student
和Address
也是非常相似的自定义类。我没有包含它们,因为它们似乎不是问题的一部分。
Date
编辑以下是我为学生班写的复制构造函数和赋值运算符,任何人都有兴趣看到/批评它们:
//Constructors
Student::Student() {
firstName = new string("Testy");
lastName = new string("Testerson");
address = new Address("000 Street St", "", "Citytown", "State", "00000");
birthDate = new Date("01/02/9876");
gradDate = new Date("01/02/6789");
gpa = new string("10.0");
crHours = new string("300");
}
Student::Student(string FN, string LN, string L1, string L2, string C, string S, string Z, string BD, string GD, string GPA, string Hr) {
firstName = new string(FN);
lastName = new string(LN);
address = new Address(L1, L2, C, S, Z);
birthDate = new Date(BD);
gradDate = new Date(GD);
gpa = new string(GPA);
crHours = new string(Hr);
}
Student::Student(string line) {
set(line);
}
//Destructors
Student::~Student() {
delete firstName;
delete lastName;
delete address;
delete birthDate;
delete gradDate;
delete gpa;
delete crHours;
}
//Member Functions
void Student::set(string line) {
firstName = new string(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
lastName = new string(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
address = new Address();
address->setLine1(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
address->setLine2(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
address->setCity(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
address->setState(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
address->setZip(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
birthDate = new Date(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
gradDate = new Date(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
gpa = new string(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
crHours = new string(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
}
void Student::printReport() {
cout << *lastName << ", " << *firstName << ", " << address->getAddress()
<< ", " << birthDate->getDate() << ", " << gradDate->getDate()
<< ", " << *gpa << ", " << *crHours << endl;
}
void Student::printName() {
cout << *lastName << ", " << *firstName << endl;
}
string Student::getName() {
return string(*lastName + ", " + *firstName);
答案 0 :(得分:2)
问题很可能发生,因为默认的复制和赋值运算符没有做正确的事 - 它们复制指针。因此,如果您在某个地方创建临时Student
,那么当该临时值被破坏时您将删除指针,而原始仍指向相同(现在已删除)的对象。当它被破坏时,它将尝试删除已经删除的指针,因此错误。
您需要遵循rule of three:如果您需要自定义析构函数,赋值运算符或复制构造函数,则需要所有这些。
将以下公开成员添加到您的班级:
Student(Student const &);
Student & operator=(Student const &);
并像这样定义它们:
Student::Student(Student const & other)
{
firstName = new string(other.firstName);
// And so on, for each pointer member.
}
Student & Student::operator=(Student const & other)
{
*firstName = other.firstName;
// And so on, for each pointer member.
return *this;
}
请注意,使用string firstName;
而不是string * firstName;
可以避免所有这些 - 在这种情况下,默认的析构函数,复制构造函数和赋值运算符会做正确的事情,你不会'需要定义其中的任何一个。
此外,请注意,您的Address
和Date
类可能会遇到同样的问题!每当你使用原始指针作为班级成员时,你必须做这些体操。
答案 1 :(得分:1)
你遇到的一个问题是你保留了Student
个对象的向量,而不是指向动态分配对象的指针向量(因此类型为Student*
)。
只需将students
变量替换为std::vector<Student*> students;
即可。从那时起,您只需push_back
由new
创建的指针。
问题是vector
已经处理了内存分配,因此推送的行(在代码中突出显示)正在将Student的副本复制到向量中的某个位置。在此之后,指向dinamically分配的对象的指针将无法访问。
正如Barmar指出的那样,拥有一个指针向量也具有能够推送Student
子类指针的优势。一个简单的例子:
class PhDStudent : public Student { ... }
students.push_back(new PhDStudent(...));
此外,您还应该在课堂上考虑许多其他调整:
您的构造函数按值string
参数,这意味着它们是从其原点深度复制的。此处最好使用const string&
,以避免不必要的副本。
正如其他一些答案所指出的那样(由Mike Seymour,cdhowie,Anton Savin以及无论是谁指出这一点),你应该遵循Rule of Three。如果您希望您的类可以复制,您还应该实现复制构造函数和复制赋值运算符。如果你正在使用C ++ 11,你也可以利用移动构造函数和移动赋值,以减少移动这些对象时的分配数量。
答案 2 :(得分:1)
看起来您并未关注Rule of Three,因此如果您复制Student
对象,就会发生糟糕的事情。具体来说,两者都将包含指向相同对象的指针,两者都会尝试删除 - 因此会尝试删除另一个已删除的对象。
赋予类有效复制语义的最简单方法是通过删除复制构造函数和复制赋值运算符来禁止复制:
class Student {
Student(Student const &) = delete;
void operator=(Student const &) = delete;
// rest of class...
};
否则,如果您希望该类可以复制,请实现这些以做一些合理的事情。
此外,这会导致内存泄漏:
students.push_back(*new Student(line));
动态创建Student
,将其复制到向量中,并丢弃指向动态对象的唯一指针。如果您要存储对象,请按一下临时副本:
students.push_back(Student(line));
或者,您可以将容器类型更改为vector<Student*>
,并记住在删除其指针时删除每个对象。 (作为指针争吵的练习,这是合理的,正如你所说的那样,但是不要在你想要维护的任何程序中进行。)
一旦你学会了手动内存管理的可怕细节,除非绝对必要,否则通过避免动态分配让自己更轻松,当你确实需要它时,总是用智能指针,容器等管理它RAII类型,不是通过处理原始指针。
答案 3 :(得分:0)
您可能不希望复制Student
,但您仍希望将它们放入vector
。在C ++ 11中,只需添加一个移动构造函数即可解决:
class Student {
public:
Student() { ...}
~Student() {...}
Student(const Student&) = delete; // optional: will be deleted implicitly
Student(Student&& other) {
firstName = other.firstName;
other.firstName = nullptr;
// ...
}
};
这样会隐式删除复制构造函数和赋值运算符,因此您无法复制。但vector
和其他容器将使用移动构造函数。这当然会对Student
使用带来某些限制。
vector<Student> students;
students.push_back(Student()); // OK, student moves
Student s;
students.push_back(s); // Error: copy constructor for Student is deleted