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

jon*_*smz 5 c++ rvalue-reference c++11

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

以这段代码为例:

#include <string>

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

int main()
{
    std::string bob("  ");
    return foo(bob);
}
Run Code Online (Sandbox Code Playgroud)

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

编译器资源管理器链接在这里:https : //godbolt.org/g/mVi9y6

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

我很想知道一个反例,其中编译器在变量超出范围之前将变量的最后一次使用转换为右值引用会在结果代码中引入一个严重的错误。如果编译器实现这种“优化”,我无法想到会中断的代码。

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

我的假设是,有是一些代码,会破坏那里的编译器实现的是“优化”,我想知道是什么代码看起来像。

我上面详述的代码是我认为会从这样的优化中受益的代码示例。

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

return foo(bob) + foo(std::move(bob));
Run Code Online (Sandbox Code Playgroud)

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

在另一个实现中,可能首先评估非移动版本,并且代码将按照非专家期望的方式运行。

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

return foo(bob) + foo(bob);
Run Code Online (Sandbox Code Playgroud)

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

这样的编译器,无论它对函数参数使用什么求值顺序,总是将在此上下文中第二次(也是最后一次)使用 bob 的情况作为右值引用进行评估,无论是左侧还是右侧操作员+。

T.C*_*.C. 4

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

// 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);
}
Run Code Online (Sandbox Code Playgroud)

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

此代码对于复制是安全的,因为run_some_async_calculation_on_vector接收从默认内存资源分配的副本(希望在整个线程的生命周期中持续存在),但会被移动完全破坏,因为这样它将保留rsrc为内存资源,一旦run_some_async_calculation返回就会消失。