通过析构函数删除时崩溃
Crash upon delete through destructor
在下面的程序中,我打算通过strcpy
将char* line
内容从一个对象复制到另一个对象。
然而,当程序结束时,obj2
的析构函数工作正常,但 obj
的析构函数崩溃。 gdb 显示两个对象 line
的不同地址。
class MyClass {
public:
char *line;
MyClass() {
line = 0;
}
MyClass(const char *s) {
line = new char[strlen(s)+1];
strcpy(line, s);
}
~MyClass() {
delete[] line;
line = 0;
}
MyClass &operator=(const MyClass &other) {
delete[] line;
line = new char[other.len()+1];
strcpy(line, other.line);
return *this;
}
int len(void) const {return strlen(line);}
};
int main() {
MyClass obj("obj");
MyClass obj2 = obj;
有
MyClass obj2 = obj;
你没有赋值,你有复制构造。而且你不遵循 rules of three, five or zero 因为你没有复制构造函数,所以默认生成的只会复制指针。
这意味着在这之后你有两个对象,它们的 line
指针都指向完全相同的内存。这将导致 未定义的行为 一旦其中一个对象被破坏,因为它使另一个对象具有无效指针。
天真的解决方案是添加一个复制构造函数,它对字符串本身进行深度复制,类似于您的赋值运算符所做的。
更好的解决方案是对字符串使用 std::string
,并遵循零规则。
您需要创建一个复制构造函数。这必须做rule of 3/5。您正在创建 obj2
,这意味着调用复制构造函数,而不是复制赋值运算符。
因为没有复制构造函数,所以创建了一个 "shallow" 副本。这意味着 line
是按值复制的。因为它是一个指针,所以 obj
和 obj2
都指向同一个内存。第一个析构函数被调用并很好地擦除该内存。调用第二个构造函数并发生双重删除,导致您的分段错误。
class MyClass {
public:
char *line = nullptr;
std::size_t size_ = 0; // Need to know the size at all times, can't
// rely on null character existing
const std::size_t MAX_SIZE = 256; // Arbitrarily chosen value
MyClass() { }
MyClass(const char *s) : size_(strlen(s)) {
if (size_ > MAX_SIZE) size_ = MAX_SIZE;
line = new char[size_];
strncpy(line, s, size_ - 1); // 'n' versions are better
line[size_ - 1] = '[=10=]';
}
MyClass(const MyClass& other) : size_(other.size_) { // Copy constructor
line = new char[size_ + 1];
strncpy(line, other.line, size_);
line[size_] = '[=10=]';
}
~MyClass() {
delete[] line;
line = nullptr;
}
MyClass& operator=(const MyClass &other) {
if (line == other.line) return *this; // Self-assignment guard
size_ = other.size_;
delete[] line;
line = new char[other.size_ + 1];
strncpy(line, other.line, size_);
line[size_] = '[=10=]';
return *this;
}
int len(void) const { return size_; }
};
在处理C-Strings的时候,绝对不能丢空字符。问题是它非常容易丢失。您的复制赋值运算符中还缺少自赋值守卫。这可能会导致你不小心用核武器攻击一个物体。我添加了一个 size_
成员并使用 strncpy()
而不是 strcpy()
因为能够指定最大字符数在丢失空字符的情况下非常重要。它不会防止损坏,但会减轻损坏。
还有其他一些我喜欢使用 Default Member Initialization(as of C++11) and using a constructor member initialization list 的东西。如果您能够使用 std::string
,那么其中很多都变得不必要了。 C++ 可以是 "C with classes",但值得花时间真正探索该语言必须提供的功能。
工作复制构造函数和析构函数允许我们做的事情是使用 "copy and swap idiom."
简化我们的复制赋值运算符
#include <utility>
MyClass& operator=(MyClass tmp) { // Copy by value now
std::swap(*this, tmp);
return *this;
}
Link to explanation.
在下面的程序中,我打算通过strcpy
将char* line
内容从一个对象复制到另一个对象。
然而,当程序结束时,obj2
的析构函数工作正常,但 obj
的析构函数崩溃。 gdb 显示两个对象 line
的不同地址。
class MyClass {
public:
char *line;
MyClass() {
line = 0;
}
MyClass(const char *s) {
line = new char[strlen(s)+1];
strcpy(line, s);
}
~MyClass() {
delete[] line;
line = 0;
}
MyClass &operator=(const MyClass &other) {
delete[] line;
line = new char[other.len()+1];
strcpy(line, other.line);
return *this;
}
int len(void) const {return strlen(line);}
};
int main() {
MyClass obj("obj");
MyClass obj2 = obj;
有
MyClass obj2 = obj;
你没有赋值,你有复制构造。而且你不遵循 rules of three, five or zero 因为你没有复制构造函数,所以默认生成的只会复制指针。
这意味着在这之后你有两个对象,它们的 line
指针都指向完全相同的内存。这将导致 未定义的行为 一旦其中一个对象被破坏,因为它使另一个对象具有无效指针。
天真的解决方案是添加一个复制构造函数,它对字符串本身进行深度复制,类似于您的赋值运算符所做的。
更好的解决方案是对字符串使用 std::string
,并遵循零规则。
您需要创建一个复制构造函数。这必须做rule of 3/5。您正在创建 obj2
,这意味着调用复制构造函数,而不是复制赋值运算符。
因为没有复制构造函数,所以创建了一个 "shallow" 副本。这意味着 line
是按值复制的。因为它是一个指针,所以 obj
和 obj2
都指向同一个内存。第一个析构函数被调用并很好地擦除该内存。调用第二个构造函数并发生双重删除,导致您的分段错误。
class MyClass {
public:
char *line = nullptr;
std::size_t size_ = 0; // Need to know the size at all times, can't
// rely on null character existing
const std::size_t MAX_SIZE = 256; // Arbitrarily chosen value
MyClass() { }
MyClass(const char *s) : size_(strlen(s)) {
if (size_ > MAX_SIZE) size_ = MAX_SIZE;
line = new char[size_];
strncpy(line, s, size_ - 1); // 'n' versions are better
line[size_ - 1] = '[=10=]';
}
MyClass(const MyClass& other) : size_(other.size_) { // Copy constructor
line = new char[size_ + 1];
strncpy(line, other.line, size_);
line[size_] = '[=10=]';
}
~MyClass() {
delete[] line;
line = nullptr;
}
MyClass& operator=(const MyClass &other) {
if (line == other.line) return *this; // Self-assignment guard
size_ = other.size_;
delete[] line;
line = new char[other.size_ + 1];
strncpy(line, other.line, size_);
line[size_] = '[=10=]';
return *this;
}
int len(void) const { return size_; }
};
在处理C-Strings的时候,绝对不能丢空字符。问题是它非常容易丢失。您的复制赋值运算符中还缺少自赋值守卫。这可能会导致你不小心用核武器攻击一个物体。我添加了一个 size_
成员并使用 strncpy()
而不是 strcpy()
因为能够指定最大字符数在丢失空字符的情况下非常重要。它不会防止损坏,但会减轻损坏。
还有其他一些我喜欢使用 Default Member Initialization(as of C++11) and using a constructor member initialization list 的东西。如果您能够使用 std::string
,那么其中很多都变得不必要了。 C++ 可以是 "C with classes",但值得花时间真正探索该语言必须提供的功能。
工作复制构造函数和析构函数允许我们做的事情是使用 "copy and swap idiom."
简化我们的复制赋值运算符#include <utility>
MyClass& operator=(MyClass tmp) { // Copy by value now
std::swap(*this, tmp);
return *this;
}
Link to explanation.