编译器推导出超出范围

时间:2017-08-18 19:38:33

标签: c++ c++11 rvalue-reference

为什么编译器不会自动推断变量是否超出范围,因此将其视为右值引用?

以此代码为例:

#include <string>

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

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

检查汇编代码清楚地表明了const&amp;在函数结束时调用“foo”的版本。

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

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

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

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

我的假设是 某些代码会破坏编译器实现“优化”的位置,而且我想知道代码是什么样的。

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

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

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

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

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

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

return foo(bob) + foo(bob);

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

这样的编译器,无论它用于函数参数的评估顺序如何,总是会在此上下文中将bob的第二个(也就是最后一个)用法评估为rvalue-reference,无论是左侧,还是操作员的右侧+。

4 个答案:

答案 0 :(得分:2)

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

// 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_calculation返回,它将消失。

答案 1 :(得分:1)

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

在这一点上进行构建:在此优化下编写“中断”代码所需的一切就是让foo的两个不同版本打印出不同的东西。而已。编译器生成的程序打印的内容不同于标准所说的。这是一个编译器错误。请注意,RVO 属于此类别,因为标准专门解决了此问题。

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

答案 2 :(得分:0)

因为它超出范围这一事实并不会使它在范围内不是一个左值。因此 - 非常合理 - 假设程序员想要第二版foo()。标准规定了这种行为AFAIK。

所以写一下:

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

...但是,如果编译器可以内联foo(),编译器可能会进一步优化代码,以获得与rvalue-reference版本相同的效果。也许

答案 3 :(得分:0)

  

为什么编译器不会自动推断变量是否超出范围,因此可以被视为右值参考?

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