请考虑以下内容。
#include <string>
using std::string;
string middle_name () {
return "Jaan";
}
int main ()
{
string&& danger = middle_name(); // ?!
return 0;
}
这段代码没有计算任何东西,但是编译没有错误,它展示了一些让我困惑的东西:danger
是一个悬空引用,不是吗?
右值引用是否允许悬垂引用?
如果你的意思是"是否可能创建悬垂的右值引用",那么答案是肯定的。然而,你的例子并没有造成这种情况。
string middle_name () {
return "Jaan";
}
int main()
{
string&& nodanger = middle_name(); // OK.
// The life-time of the temporary is extended
// to the life-time of the reference.
return 0;
}
是完全没有问题的。同样的规则也适用于这里,使得这个例子(Herb Sutter的文章)也是安全的。如果你使用纯右值来初始化引用,则临时对象的生命周期将延长到引用的生命周期。但是,你仍然可以产生悬空引用。例如,下面的代码就不再安全:
int main()
{
string&& danger = std::move(middle_name()); // dangling reference !
return 0;
}
因为 std::move
返回的是一个string&&
(它不是纯正rvalue),所以延长临时对象寿命的规则不适用。在这里,std::move
返回了一个被称为xvalue的东西。一个xvalue只是一个未命名的rvalue引用。因此,它可以指代任何东西,基本上无法猜测返回的引用指向什么,除非查看函数的实现。
std::move
没有实际意义。std::move 的结果引用同一对象的信息在函数类型中不会被保留。 - sellibitzervalue引用绑定到rvalue。 rvalue可以是prvalue或xvalue [解释]。绑定到前者永远不会创建悬空引用,而绑定到后者可能会。这就是为什么通常不建议选择T&&
作为函数的返回类型。 std::move
是此规则的例外。
T& lvalue();
T prvalue();
T&& xvalue();
T&& does_not_compile = lvalue();
T&& well_behaved = prvalue();
T&& problematic = xvalue();
danger
是一个悬空引用,对吗?
与使用const &
一样,并不会出现悬空引用的情况:danger
将拥有这个右值。
#include <cstdio>
#include <tuple>
std::tuple<int&&> mytuple{ 2 };
auto pollute_stack()
{
printf("Dumdudelei!\n");
}
int main()
{
{
int a = 5;
mytuple = std::forward_as_tuple<int&&>(std::move(a));
}
pollute_stack();
int b = std::get<int&&>(mytuple);
printf("Hello b = %d!\n", b);
}
输出:
Dumdudelei!
Hello b = 0!
正如你所看到的,b现在有了错误的值。为什么?我们将一个右值引用赋给了自动变量a,并将其放入全局元组中。然后我们逃离了a的作用域,并通过std::get<int&&>检索其值,这将评估为右值引用。因此,新对象b实际上是从a移动构造的,但编译器找不到a,因为它的作用域已经结束了。因此,std::get<int&&>
评估为0(尽管它可能是未定义行为,并且可以评估为任何内容)。
请注意,如果我们不触及堆栈,右值引用实际上仍然会在其作用域结束后找到对象a的原始值,并检索正确的值(只需尝试并取消注释pollute_stack(),看看会发生什么)。pollute_stack()函数只是通过printf()执行一些与io相关的操作,同时将堆栈指针向前和向后移动,写入堆栈值。
然而,编译器完全无法看穿这一点,因此请注意这一点。
int &&variable_or_dummy = modify_var? move(var) : int();
- Potatoswatter