C++:string模拟实现中的赋值拷贝函数现代写法诡异地崩掉了......
事情是这样的:
博主今天回看以前实现过的string,当时就遇到了一个bug:
可见博主当时的破防。因为最近在集中复盘C++初阶部分,就有点好奇年轻的时候自己写的模拟string是什么样。没想到给我自己留了个bug。
现在来细看这个场景:为了测试自己写的赋值拷贝现代写法效果,有了这个函数。
void test11(){//现代构造的写法string s("helloworld");string s2(s);// 现代拷贝构造cout << s2 << endl;string s3("cherry magic");s2 = s3;// 现代赋值拷贝cout << s2 << endl;}
当来到165行时——即我打算让s3赋值给现有的对象s2,此时自然要调用我亲手写的赋值拷贝。


当我按F11,企图跳进operator=里时,却是来到了拷贝构造这里。
嗯?此举何意啊——正常意料:我亲手写的赋值拷贝operator=传的参数是传值传参(传值(自定义类型string)传参都要雷打不动先拷贝构造一份)

没错,拷贝构造我也写出了现代写法——避免这里长篇大论,现代写法我几句话说个大概,说不定读者看完就能上手写。
string tmp(s.c_str());//tmp建立在当前函数栈上,等当前函数结束,函数栈帧被销毁,生命周期也结束了。
当这句话执行完,tmp就是存在于当前栈上,但内容是拷贝s的string对象。我们拷贝构造函数的目的,就是让当前的*this(未构造的对象)拷贝传入对象s的内容,完成对象初始化。
无论如何,当拷贝构造函数结束,*this是完全实例化,tmp却要走向独属它的落幕(因为tmp是string自定义类型对象,它自动会调用析构完成资源释放)。
tmp拷贝完了,*this还没拷贝。怎么办?把*this有个大胆的想法——
swap(tmp);// 完整写 this->swap(tmp); 或者 (*this).swap(tmp);
看我的批注:*this用自己的成员函数将tmp和自己交换——交换了拷贝的内容。*this就这么轻轻一换完成了拷贝构造,tmp就拿着本来无用的信息去析构了。
我们继续按F11,等此次拷贝构造结束——跳入operator=的参数就构造完成,我们就进入operator=看看问题出在哪。



tmp此时该走向独属于它的落幕——身为string家中的长(栈)子,它有自己的使命(*this是次子(bushi



其实刚刚那一步,可能有读者看出不对劲了。tmp交换过来的内容,应该是完全没被初始化的——怎么可能会有需要的释放的资源?那个_str也是生得奇怪——恰好是随机值,躲过了判空检查,所以避无可避地执行了delete[] _str;
我今天在改这个代码的时候,其实没注意到这点:因为出了构造函数完成了参数的拷贝构造,我想着此时该进入operator=了。直接按下了F10——跳过了tmp的析构,直接出现了这一幕。
所以下意识判断是operaor=的问题:这也是为什么我会当时写下这句判断——

真相是什么?
今天在改代码的时候,我发现因为*this天生生得潦草,_str这个指针怎么生都是随机值——所以tmp换到它的内容,必走析构里的delete[] _str;——这件事让我很是苦恼,知道哪里出问题了,却是下不了手。
有没有什么方法?有的,只是我忘了(我忏悔)

初始化列表
我今天也是开眼了,拷贝构造也是构造。只要是构造函数,类型的成员变量就会走初始化列表——初始化列表存在的意义就是对成员变量定义。
身为变量,有了声明自然要有定义。所以不管显示写初始化列表与否,成员变量都会被定义(即成员变量都会走初始化列表)。初始化列表是很客观的存在,不是我们不写就不存在的。
如果显示写初始化列表,相应成员变量都会被规定的值初始化;
如果不显示写,可以在声明的位置给出缺省值——没错,这里的缺省值就是给未显示写在初始化列表的成员进行定义
- 如果声明都不给缺省值了,那编译器表示:“那我随意了”——对于内置类型,编译器会给它随机值(取决于编译器,行为不确定);对于自定义类型,会去调用它的默认构造(如果没有默认构造,会编译报错)
想起来了,我再看我的string成员变量声明——果然没有声明值,而且刚刚的拷贝构造也没显示初始化列表(完美地避开正确初始化机会)


再看效果:

我发现AI摘要比我写得完整,但我想没我写得有趣:(对只是我想)
摘要:博主复盘自己早期实现的string类时,发现了一个隐藏bug。问题出现在拷贝构造函数的现代写法中:当交换临时对象tmp和未初始化的*this时,由于未显式初始化_str成员变量,导致析构时delete了一个随机地址。通过给成员变量添加缺省值,解决了这个因未初始化引发的未定义行为。文章详细记录了调试过程,揭示了C++构造函数初始化列表的重要性,以及显示初始化成员变量的必要性。最终修复方案是为string类的成员变量添加了缺省值声明,使程序得以正确运行。