我正在尝试理解rvalue引用并移动C 11的语义 .
这些示例之间有什么区别,哪些不会执行矢量复制?
第一个例子
std::vector<int> return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return tmp;
}
std::vector<int> &&rval_ref = return_vector();
第二个例子
std::vector<int>&& return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
第三个例子
std::vector<int> return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
5 回答
它们都不会复制,但第二个会引用一个被破坏的矢量 . 命名的右值引用几乎从不存在于常规代码中 . 你只需要在C 03中写一份副本就可以了 .
除了现在,向量移动 . 在绝大多数情况下,类的用户不会使用rvalue引用 .
简单的答案是你应该为常规引用代码编写rvalue引用的代码,并且你应该在99%的时间内在精神上对它们进行相同的处理 . 这包括有关返回引用的所有旧规则(即永远不会返回对局部变量的引用) .
除非您正在编写需要利用std :: forward并且能够编写采用左值或右值引用的泛型函数的模板容器类,否则这或多或少都是正确的 .
移动构造函数和移动赋值的一个重要优点是,如果定义它们,编译器可以在RVO(返回值优化)和NRVO(命名返回值优化)无法调用的情况下使用它们 . 这对于通过方法有效地返回昂贵的对象(如容器和字符串)非常重要 .
现在,rvalue引用让事情变得有趣,你也可以将它们用作普通函数的参数 . 这允许您编写具有const引用(const foo和other)和rvalue引用(foo && other)重载的容器 . 即使参数过于笨拙而无法通过构造函数调用,它仍然可以完成:
STL容器已更新为几乎任何东西都有移动重载(散列键和值,矢量插入等),并且您将在这里看到它们 .
您也可以将它们用于普通函数,如果只提供右值引用参数,则可以强制调用者创建对象并让函数执行移动 . 这是一个非常好用的例子,但在我的渲染库中,我为所有加载的资源分配了一个字符串,这样就可以更容易地看到每个对象在调试器中代表什么 . 界面是这样的:
它是一种“漏洞抽象”的形式,但允许我利用我必须在大多数时间创建字符串的事实,并避免再次复制它 . 这不是完全高性能的代码,但是当人们掌握这一功能时,这是一个很好的例子 . 这段代码实际上要求变量是调用的临时变量,或调用std :: move:
要么
要么
但这不会编译!
本身不是答案,而是指南 . 在大多数情况下,声明本地
T&&
变量没有多大意义(就像你使用std::vector<int>&& rval_ref
一样) . 您仍然需要std::move()
才能在foo(T&&)
类型方法中使用它们 . 还有一个问题已经提到,当你试图从函数返回这样的rval_ref
时,你将得到标准的被引用到被破坏的临时惨败 .大多数时候我会采用以下模式:
您没有对返回的临时对象持有任何引用,因此您可以避免(没有经验的)程序员的错误,他们希望使用移动的对象 .
显然有(虽然很少见)一个函数真正返回
T&&
的情况,它是对可以移动到对象中的非临时对象的引用 .关于RVO:这些机制通常可以工作,编译器可以很好地避免复制,但是在返回路径不明显的情况下(异常,
if
条件确定您将返回的命名对象,可能还有其他)rrefs是您的救星(即使可能更贵) .第一个例子
第一个示例返回一个由
rval_ref
捕获的临时 . 那个临时的生命将超出rval_ref
定义,你可以使用它,好像你已经按 Value 捕获它 . 这非常类似于以下内容:除了在我的重写中,你显然不能以非const方式使用
rval_ref
.第二个例子
在第二个示例中,您创建了一个运行时错误 .
rval_ref
现在持有对函数内部被破坏的tmp
的引用 . 运气好的话,这段代码会立即崩溃 .第三个例子
你的第三个例子大致相当于你的第一个例子 .
tmp
上的std::move
是不必要的,实际上可能是性能下降,因为它会抑制返回值优化 .编码您正在做的事情的最佳方法是:
最好实践
即就像你在C 03中那样.
tmp
在return语句中被隐含地视为rvalue . 它将通过返回值优化(无复制,无移动)返回,或者如果编译器决定它不能执行RVO,则它will use vector's move constructor to do the return . 仅当未执行RVO时,并且如果返回的类型没有移动构造函数,则复制构造函数将用于返回 .这些都不会做任何额外的复制 . 即使没有使用RVO,新标准也表示移动结构在做回报时比我认为的更好 .
我相信你的第二个例子会导致未定义的行为,因为你正在返回对局部变量的引用 .