Effective Modern C++ 中的第 29 条,Scott Meyers 列出了移动语义不能提高代码性能的三种场景,
[…]移动语义对你没有好处:
- 无移动操作:要移动的对象无法提供移动操作[…]
- 移动速度不快:[…]移动操作不比复制操作快。
- 移动不可用: 上下文 […] 需要一个不发出异常的移动操作,但该操作未声明
。noexcept
前几页都解释清楚了,再补充一下
[…]移动语义没有提供效率增益的另一种情况:
- 源对象是左值:除了极少数例外(参见例如第 25 条),只有右值可以用作移动操作的源。
(第 25 项的标题为 在右值引用上使用
std::move
,在通用引用上使用 std::forward
,但我不明白它与交叉引用它的项目符号点有什么关系。)
在此之后,文本本质上回到了对该项目的总结,没有进一步提及第四个要点。该要点指的是什么?
据我了解移动语义,即使我想从左值移动,比如
x
,我仍然需要通过
std::move(x)
(或等效的
static_cast
)将其转换为右值,所以我从技术上讲,仍然是从右值(特别是本例中的 x 值),而不是左值开始。所以我想说左值不能是移动操作的源对象。
我对这个主题缺少什么?
lvalue 指的是某种“命名”值,即具有多个引用的实体。移动语义并不真正适用于它们,因为您不应该“窃取”可能在其他地方引用的内容的表示。也就是说,如果源对象是左值,那么您就永远不会移动!因此,移动构建在这里并没有提供任何好处。事实上,左值不会自愿绑定到右值引用 - 您必须强制该绑定,例如,通过使用 std::move()
。本质上,你的观点是完全正确的:左值不能是移动操作的源 - 因此移动操作不会在涉及左值的情况下提供好处。
编译器可能会省略本地对象的复制(或移动)[...]
Widget makeWidget() { Widget w; //… return w; }
[…] 每个像样的 C++ 编译器都会使用 RVO 来避免复制
w
。[…]如果满足 RVO 的条件,但编译器选择不执行复制省略,则返回的对象必须被视为右值。实际上,该标准要求当允许 RVO 时,要么发生复制省略,要么隐式应用
std::move
[…]
T x;
x.a = ...;
x.b = ...;
x.c = ...;
return x;
然后将构造一个对象 x,通过 return 语句创建一个新的未命名对象,然后 x 被析构,然后移动返回值。最终调用者将为移动的结果调用析构函数。所以你有两个构造函数,两个析构函数,没有节省。如果你开始
T x(a, b, c);
return x;
然后你有同样的问题,两个构造函数和析构函数,没有节省。要真正保存任何内容,您需要编写
return T(a, b, c);
或返回另一个返回对象的函数的返回值。