作者回复: 很棒👌。
异常安全是关键。
作者回复: 3的正解终于出现,有人说到“异常安全”了。👍
再说两句,这是C++98时设计的接口,没有移动就只能那样。有了移动,在多线程的环境里,移动返回加弹出实际上就变得有用了。我对复杂和可读性部分不那么同意。
作者回复: catch住也没有用了。仔细想一下,我现在要把vector里的两个对象移到一个新的vector,移第一个成功,第二个时有异常,然后vector该怎么办?现在两个vector都废掉了。
拷贝不影响旧的容器。即使发生异常,至少老的那个还是好的。这就是异常安全。
作者回复: 挺好。三比其他回答已经进一步了,但还是没有触及到某个关键字。
作者回复: 关于几次拷贝/移动的问题?参考 hello world 的评论下的廖熊猫的回答:
在插入的时候,你会发现空间不够了,然后开辟新的空间,在新空间先把最后插入的元素放好,然后再依次把以前的元素一个一个挪过来。空间不够的话最后一个元素是没法插入进去的啊,所以没办法移动三次的。
还有我自己的回答:
两者都是要构造第 3 个对象时空间不足,需要这样:
1. 分配一个足够大的新内存区域。
2. 在上面构造第 3 个对象。
3. 如果成功(没有异常),再移动/拷贝旧的对象。
4. 全部成功,则析构旧对象,释放旧对象的内存。
5. 如果 1 出现异常,直接抛出即可;如果 2–3 出现异常,则析构已成功构造的对象,释放新内存空间,继续抛出异常。
如果不是这个问题。请把问题阐释得更详细些。可以重新开一个新的评论。
作者回复: 学习很认真,回答也基本抓住要点了,尤其问题 2 和 3。👍
作者回复: 头两个在已有空间上成功构造。第三个时发现空间不足,系统会请求更大的空间,大小由实现决定(比如两倍)。有了足够的空间后,就会在新空间的第三个的位置构造(第三个obj1),成功之后再把头两个拷贝或移动过来。
作者回复: 有啊,最基本的原因就是多态了:如果你存进去的对象实际是 circle、triangle 等不同对象的话,你声明时用 shared_ptr<shape> 应该就比较合适了。
作者回复: 这不正是正文里讨论移动构造函数不应该抛异常这个问题么。之所以push_back时没这个要求,因为本来这儿就是临时对象,移动不成功也没有特别的损失。移动现有的元素如果失败了,那vector的状态就彻底混乱了。
作者回复: 只有const char*,目标是const string&,会引发一个临时string的构造,会导致内存复制。
用string_view当然也会产生临时对象,但string_view不会复制字符串的内容。
作者回复: 我觉得参考资料里的 cppreference.com 就很好。当然不是离线的。
作者回复: OK,基本说到点子上了。
作者回复: 这个疑问提得好👍。在目前这个例子里,确实是移动构造而不是拷贝构造。
作者回复: 会将前面的元素拷贝或移动一遍。
移动的条件文中提到了,元素类型需要“提供一个保证不抛异常的移动构造函数”。
作者回复: 多谢多谢。已经修复。
作者回复: https://www.cnblogs.com/skyfsm/p/7488053.html
这篇中文文章说得还比较清楚,可以看一下。
作者回复: 不是。执行 v1.reserve(2) 之后,空间大小就是 2 了。扩充空间是一个编译器自发进行的操作,没有用户控制。一般会类似于 reserve(size() * 2)。
作者回复: 对,缺省情况下,std::terminate 会被调用。
作者回复: 就像你在代码里对 str 定义两次一样啊。这仍然是 C++,不是 Python。
Restart kernel 可以重新来。
作者回复: Java是引用语义,返回对象就是返回个指针,没有任何问题。
C++是值语义,以前返回对象只能是拷贝,可能发生异常。一旦发生异常,对象已经被弹出,那它就彻底“丢失”了。