SS's Trace

Sirius's Blog

学习右值引用时,与 std::vector 同时使用时遇到的一些问题

先简单说一下右值引用

右值引用的基础知识,在这篇文章中说的很清楚,建议仔细阅读。

https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/index.html

简单来说,右值主要实现了转移语义和完美转发。一个是避免了拷贝,一个是使得将一组参数原封不动的传递给另一个函数方便实现。

此文搁置的时间比较久了,现在把图片补齐重新发一下。

过程

我们容易写出以下简单的类:

此类实现了最基本的默认构造函数、拷贝构造函数、移动构造函数、赋值运算符、移动赋值运算符。这里只是说明一下各函数声明。

将其丰富一下,让 test 类持有资源:

初始化问题

如果我们初始化一个具有一个test实例的vector,有两种方法:

第一种使用 initializer_list ,第二种使用默认构造方法后 push_back 。

对于第一种,使用的构造函数为

《学习右值引用时,与 std::vector 同时使用时遇到的一些问题》

这里会发生一次拷贝,具体原因我现在无法准确讲清楚。initializer_list 虽然按值传递,但在传入函数体过程中并不会发生深拷贝。

对于第二种,使用的 push_back  函数为

《学习右值引用时,与 std::vector 同时使用时遇到的一些问题》

因为重载了移动构造函数,这里直接调用它,不发生拷贝。

vector 扩容问题

为了方便观察拷贝次数,现在对代码做一点修改,使得拷贝时会在字符串后面附加一个下划线(不是一个好做法)。

《学习右值引用时,与 std::vector 同时使用时遇到的一些问题》

不难看出,此处的 vector 在扩容时疯狂进行拷贝。

为什么 vector 在此处扩容时会选择复制而不是移动呢?我们能不能让他移动?

考虑一下情景:假设我们使用移动策略,那么如果 vector 在扩容后,在移动的过程中移动构造函数抛出异常。此时问题将变得难以处理,原位置的资源已经被移动,而 vector 需要保证异常后还能维持数据不变。

解决方法也很简单,给移动构造函数加上 noexcept。注意,必须当此函数不可能抛出异常方可。

Ref: https://stackoverflow.com/questions/15730992/why-does-resize-cause-a-copy-rather-than-a-move-of-a-vectors-content-when-c

《学习右值引用时,与 std::vector 同时使用时遇到的一些问题》

可以看到此时已不存在多余的拷贝。

CC BY-NC-SA 4.0 本作品使用基于以下许可授权:Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注