在以下程序中,我打算通过将char* line内容从一个对象复制到另一个对象strcpy。但是,当程序结束时,析构函数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;
Run Code Online (Sandbox Code Playgroud)
Som*_*ude 13
用
MyClass obj2 = obj;
Run Code Online (Sandbox Code Playgroud)
您没有分配,您有copy-construction。而且由于您没有复制构造函数,因此您不遵循三,五或零的规则,因此默认生成的一个将仅复制指针。
这意味着在此之后,您将拥有两个对象,它们的line指针都指向完全相同的内存。一旦其中一个对象被销毁,将导致未定义的行为,因为对象离开另一个对象时带有无效的指针。
天真的解决方案是添加一个复制构造函数,该构造函数对字符串本身进行深复制,类似于您的赋值运算符正在执行的操作。
更好的解决方案是std::string改用您的字符串,并遵循零规则。
您需要创建一个复制构造函数。这必须遵循3/5 规则。您正在创建obj2,这意味着调用复制构造函数,而不是复制赋值运算符。
因为您没有复制构造函数,所以会创建“浅”副本。这意味着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] = '\0';
}
MyClass(const MyClass& other) : size_(other.size_) { // Copy constructor
line = new char[size_ + 1];
strncpy(line, other.line, size_);
line[size_] = '\0';
}
~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_] = '\0';
return *this;
}
int len(void) const { return size_; }
};
Run Code Online (Sandbox Code Playgroud)
处理 C 字符串时,绝对不能丢失空字符。问题是它非常容易丢失。您的复制赋值运算符中还缺少自赋值保护。这可能会导致你意外地用核武器攻击某个物体。我添加了一个size_成员并使用了strncpy()代替strcpy(),因为在丢失空字符的情况下能够指定最大字符数非常重要。它不会防止损害,但会减轻损害。
我还做了一些其他的事情,比如使用默认成员初始化(从 C++11 开始)和使用构造函数成员初始化列表。如果你能够使用的话,很多事情就变得不必要了std::string。C++ 可以是“带有类的 C”,但值得花时间真正探索该语言所提供的功能。
工作复制构造函数和析构函数允许我们做的事情是使用“复制和交换习惯用法”来简化复制赋值运算符。
#include <utility>
MyClass& operator=(MyClass tmp) { // Copy by value now
std::swap(*this, tmp);
return *this;
}
Run Code Online (Sandbox Code Playgroud)