编译器推导超出范围的变量的右值引用

Compiler deduction of rvalue-references for variables going out of scope

为什么编译器不会自动推断出变量即将超出范围,从而将其视为右值引用?

以这段代码为例:

#include <string>

int foo(std::string && bob);
int foo(const std::string & bob);

int main()
{
    std::string bob("  ");
    return foo(bob);
}

检查汇编代码清楚地表明 "foo" 的 const & 版本在函数末尾被调用。

编译器资源管理器 link 此处:https://godbolt.org/g/mVi9y6

编辑:澄清一下,我不是在寻找移动变量的替代方法的建议。我也不想理解为什么编译器选择 foo 的 const& 版本。这些都是我理解的很好。

我想知道一个反例,在该反例中,编译器在变量超出范围之前将其最后一次使用转换为右值引用会在生成的代码中引入严重的错误。如果编译器实现此 "optimization".

,我想不出代码会中断

如果编译器自动将即将超出范围的变量的最后一次使用设为右值引用时没有代码中断,那么为什么编译器不将其实现为优化?

我的假设是 一些代码会在编译器实现 "optimization" 的地方中断,我想知道这些代码是什么样的。

我在上面详述的代码是一个代码示例,我相信它会从这样的优化中受益。

函数参数的计算顺序,例如 operator+(foo(bob), foo(bob)) 是实现定义的。因此,代码如

return foo(bob) + foo(std::move(bob));

很危险,因为您使用的编译器可能会首先计算 + 运算符的右侧。这将导致字符串 bob 可能被移动,并使其处于有效但不确定的状态。随后,将使用生成的修改后的字符串调用 foo(bob)。

在另一个实现中,可能首先评估非移动版本,并且代码的行为方式与非专家所期望的方式相同。

如果我们假设某些未来版本的 c++ 标准实现了允许编译器将变量的最后一次使用视为右值引用的优化,那么

return foo(bob) + foo(bob);

毫无意外地工作(假设 foo 的适当实现,无论如何)。

这样的编译器,无论它对函数参数使用何种求值顺序,都会始终将 bob 在此上下文中的第二次(也是最后一次)用法作为右值引用进行求值,无论那是左侧, 或运算符的右侧 +.

因为它会超出范围这一事实并不能使它在范围内时成为非左值。所以 - 非常合理 - 假设是程序员想要 foo() 的第二个版本。标准要求这种行为 AFAIK。

所以只写:

int main()
{
    std::string bob("  ");
    return foo(std::move(bob));
}

... 但是,如果编译器可以内联 foo(),则编译器可能能够进一步优化代码,以获得与右值引用版本大致相同的效果。也许。

Why won't the compiler automatically deduce that a variable is about to go out of scope, and can therefore be considered an rvalue-reference?

调用函数时,变量仍在范围内。如果编译器根据函数调用后发生的情况改变哪个函数更适合的逻辑,那就违反了标准。

你的问题的答案是因为标准规定不允许这样做。编译器只能在 as if 规则下进行优化。 String 有一个很大的构造函数,因此编译器不会进行它需要的验证。

在这一点上稍做改进:在这种优化下编写 "breaks" 的代码所需要做的就是让 foo 的两个不同版本打印不同的内容。而已。编译器生成的程序打印的内容与 标准所说的 不同。那是一个编译器错误。请注意,RVO 属于此类别,因为标准专门针对它。

问为什么标准没有这样说可能更有意义,e.g.why 没有扩展函数末尾返回的规则,它被隐式地视为右值。答案很可能是因为定义正确的行为会很快变得复杂。如果最后一行是 return foo(bob) + foo(bob),你会怎么做?等等。

这是一段完全有效的现有代码,您的更改会破坏它:

// launch a thread that does the calculation, moving v to the thread, and
// returns a future for the result
std::future<Foo> run_some_async_calculation_on_vector(std::pmr::vector<int> v); 

std::future<Foo> run_some_async_calculation() {
    char buffer[2000];
    std::pmr::monotonic_buffer_resource rsrc(buffer, 2000);
    std::pmr::vector<int> vec(&rsrc);
    // fill vec
    return run_some_async_calculation_on_vector(vec);
}

移动构建容器总是传播其分配器,但复制构建容器则不必,并且polymorphic_allocator在容器上传播的分配器复制构造。相反,它总是恢复到默认内存资源。

此代码在复制时是安全的,因为 run_some_async_calculation_on_vector 接收到从默认内存资源分配的副本(希望它在整个线程的生命周期中持续存在),但是被一个移动完全破坏了,因为那样它会保留rsrc作为内存资源,run_some_async_calculationreturns.

一次就会消失