自定义Mystring类移动赋值运算符异常导致后续输出失效问题求助
我写了一个自定义的Mystring类来练习C++的拷贝和移动语义,代码执行到larry4 = "larry4";都符合预期,但执行larry4 = Mystring{"larry4Again"};之后,后续的larry4.display();、std::cout << "after" << endl;都不工作了,而且我预期的参数化构造函数(arg ctor)+ 移动赋值运算符的输出也没正常走完,完全搞不懂哪里出问题了。
以下是我的代码:
main.cpp
int main() { Mystring larry3 = "larry3"; //will call no-arg ctor not move ctor. Mystring larry4 = larry3; // deep copy ctor is being called correctly std::cout <<"before" << endl; larry4 = "larry4"; // move assignment operator is being called correctly larry4 = Mystring{"larry4Again"}; // move assignment should be called -- but things // seems to be messed after this line larry4.display(); // this is also not working std::cout <<"after" << endl; // this is also not working return 0; }
Mystring.cpp
// // Created by kumarg on 17-11-2024. // #include "Mystring.h" #include <iostream> #include <cstring> //no-arg ctror Mystring::Mystring() :str{nullptr} { str = new char[1]; *str = '\0'; // cout << "no-arg ctor" << endl; } //arg ctor Mystring::Mystring(const char *s) :str{nullptr}{ if(s == nullptr) { //if s points to nullptr then do same thing as no-arg ctor this->str = new char[1]; // this->str or str is same used in 3rd line from here *str = '\0'; } else { str = new char[std::strlen(s) + 1]; std::strcpy(this->str, s); } std::cout << "arg-ctor" <<endl; } //dtor Mystring::~Mystring() { std::cout << "dtor called: " << str << endl; delete []str; } void Mystring::display() const { std::cout << this->str << endl; } //copy ctor - deep Mystring::Mystring(const Mystring &source) :str{nullptr} { std::cout << "deep copy ctor" << endl; str = new char[std::strlen(source.str) +1]; std::strcpy(this->str, source.str); } //move ctor Mystring::Mystring(Mystring &&source) :str{source.str} { cout << "move ctor" << endl; source.str = nullptr; } //copy assignment operator implementation Mystring &Mystring::operator=(const Mystring &rhs) { std::cout << "copy assignment operator" << endl; //check if both are same already if(this == &rhs) return *this; delete []str; str = new char[std::strlen(rhs.str) + 1]; std::strcpy(this->str, rhs.str); return *this; } //move assignment operator Mystring &Mystring::operator=(Mystring &&rhs) { std::cout << "move assignment operator" << endl; if(this == &rhs) return *this; delete []str; str = rhs.str; rhs.str = nullptr; return *this; }
Mystring.h
// // Created by kumarg on 16-11-2024. // #ifndef MYSTRING_H #define MYSTRING_H #include <iostream> using namespace std; class Mystring { private: char *str; public: Mystring(); // default ctor Mystring(const char *source); //parameter ctor ~Mystring(); Mystring(const Mystring &source); //copy ctor Mystring(Mystring &&source) noexcept; //move ctor void display() const; Mystring operator-(); // unary - operator overloading bool operator==(Mystring &rhs); // assignment operator overloading bool operator!=(Mystring &rhs); // !- operator overloading bool operator<(Mystring &lhs); bool operator>(Mystring &lhs); Mystring operator+(Mystring &lhs); //assignment (=) operator overloading, remember this called when we do s2 = s1(both are initilaized already) // = operator can be overloaded in 2 ways, copy and move, if l-value thenp copy will be called, if r-value move will be called Mystring &operator=(const Mystring &rhs); // = op overloading by move Mystring &operator=(Mystring &&rhs); //Mystring &operator+=(Mystring &lhs); }; #endif //MYSTRING_H
问题分析与解决办法
你遇到的核心问题是析构函数中的未定义行为导致程序崩溃,进而使得后续代码无法执行,咱们一步步理清楚:
当执行larry4 = Mystring{"larry4Again"};时,实际会发生这些步骤:
- 创建临时Mystring对象
Mystring{"larry4Again"},调用参数化构造函数(arg-ctor),此时临时对象的str指向堆上的"larry4Again"字符串内存。 - 触发移动赋值运算符,把临时对象的
str所有权转移给larry4,同时将临时对象的str设置为nullptr(这是移动语义的标准操作,避免双份释放)。 - 临时对象的生命周期结束,触发析构函数。
而你的析构函数里有这行代码:
std::cout << "dtor called: " << str << endl;
C++标准里明确规定,用std::cout输出一个nullptr类型的char*属于未定义行为——不同编译器的处理逻辑不一样,有的会输出(null),有的直接会导致程序崩溃。一旦程序崩溃,后续的larry4.display();和cout << "after"自然就没机会执行了。
另外还有两个小细节需要修正,养成良好的编码习惯:
移动构造函数的实现要和头文件声明一致,加上
noexcept:Mystring::Mystring(Mystring &&source) noexcept :str{source.str} { cout << "move ctor" << endl; source.str = nullptr; }标准库容器在使用移动语义时会依赖
noexcept来保证异常安全性,虽然这里没用到容器,但保持声明和实现一致很重要。像
operator==、operator!=这类比较运算符,应该把参数设为const引用,同时成员函数本身也设为const,这样才能支持对const对象调用这些方法:// 头文件里的声明要改成这样 bool operator==(const Mystring &rhs) const; bool operator!=(const Mystring &rhs) const; bool operator<(const Mystring &rhs) const; bool operator>(const Mystring &rhs) const; Mystring operator+(const Mystring &rhs) const;
修复步骤
最关键的是修改析构函数,先判断str是否为nullptr再输出:
Mystring::~Mystring() { std::cout << "dtor called: "; if (str != nullptr) { std::cout << str; } else { std::cout << "nullptr"; } std::cout << endl; delete []str; }
修改后再运行代码,你就能看到预期的输出顺序了:
arg-ctor move assignment operator dtor called: nullptr larry4Again after
后续的display()和cout也能正常执行啦。
备注:内容来源于stack exchange,提问作者gaurav s

