为什么移动语义必须删除临时副本?

AUD*_*IUV 4 c++ rvalue-reference move-semantics copy-elision c++11

因此,我对移动语义的理解是,它们允许您覆盖用于临时值(rvalues)的函数,并避免可能昂贵的副本(通过将状态从未命名的临时值移动到命名的左值).

我的问题是为什么我们需要特殊的语义?为什么C++ 98编译器不能忽略这些副本,因为编译器确定给定表达式是左值还是左值?举个例子:

void func(const std::string& s) {
    // Do something with s
}

int main() {
    func(std::string("abc") + std::string("def"));
}
Run Code Online (Sandbox Code Playgroud)

即使没有C++ 11的移动语义,编译器仍应能够确定传递给它的表达式func()是否是rvalue,因此不需要临时对象的副本.那为什么要有这个区别呢?似乎移动语义的这种应用本质上是复制省略或其他类似编译器优化的变体.

作为另一个例子,为什么要打扰如下代码呢?

void func(const std::string& s) {
    // Do something with lvalue string
}

void func(std::string&& s) {
    // Do something with rvalue string
}

int main() {
    std::string s("abc");

    // Presumably calls func(const std::string&) overload
    func(s);

    // Presumably calls func(std::string&&) overload
    func(std::string("abc") + std::string("def"));
}
Run Code Online (Sandbox Code Playgroud)

似乎const std::string&重载可以处理这两种情况:lvalues像往常一样,rvalues作为const引用(因为临时表达式按照定义是const类).由于编译器知道表达式何时是左值或右值,因此可以决定是否在rvalue的情况下忽略副本.

基本上,为什么移动语义被认为是特殊的,而不仅仅是一个可以由前C++ 11编译器执行的编译器优化?

Chr*_*eck 8

移动功能不会完全忽略临时副本.

存在相同数量的临时值,只是通常调用移动构造函数而不是通常调用复制构造函数,允许移除原始构造函数而不是创建独立副本.这有时会更有效率.

C++形式对象模型完全不受移动语义的修改.对象仍然具有明确定义的生命周期,从某个特定地址开始,并在它们被销毁时结束.他们在一生中从不"移动".当它们"被移出"时,真正发生的事情就是从一个计划很快死亡的物体中挖出胆量,并将其有效地放置在一个新物体中.它可能看起来像他们移动,但正式地说,他们并没有真正,因为这将彻底打破C++.

被赶出去不是死亡.需要移动才能使对象处于"有效状态",并且它们仍处于活动状态,并且稍后将始终调用析构函数.

删除副本是完全不同的事情,在某些临时对象链中,某些中间体被跳过.编译器不需要在C++ 11和C++ 14中删除副本,即使它们可能违反通常指导优化的"as-if"规则,也允许它们执行此操作.即使复制ctor可能有副作用,高优化设置的编译器仍可能跳过一些临时工具.

相比之下,"保证复制椭圆"是一种新的C++ 17特性,这意味着该标准要求在某些情况下进行复制椭圆.

移动语义和复制椭圆提供两种不同的方法,以在这些"临时链"场景中实现更高的效率.在移动语义中,所有的临时工仍然存在,但我们不是调用复制构造函数,而是调用(希望)较便宜的构造函数,移动构造函数.在复制椭圆中,我们可以一起跳过一些对象.

基本上,为什么移动语义被认为是特殊的,而不仅仅是一个可以由前C++ 11编译器执行的编译器优化?

移动语义不是"编译器优化".它们是类型系统的新部分.移动语义甚至当你与编译发生-O0gccclang-它会导致不同的功能被调用,因为,事实上,其目的在于将死现在是在引用的类型"注释".它允许"应用程序级优化",但这与优化程序的功能不同.

也许你可以把它想象成一个安全网.当然,在理想的世界中,优化器总是会消除每个不必要的副本.但是,有时候,构造一个临时的很复杂,涉及动态分配,而编译器并没有全面了解它.在许多这样的情况下,您将通过移动语义来保存,这可能允许您完全避免进行动态分配.这反过来可能导致生成的代码更容易被优化器分析.

保证复制椭圆的东西有点像,他们找到了一种方法来形式化关于临时工的一些"常识",这样更多的代码不仅可以按照你期望的方式进行优化,而且需要以你的方式工作期待它被编译,并且当你认为不应该真正复制时不要调用复制构造函数.因此,您可以通过工厂函数的值返回不可复制的不可移动类型.编译器发现,在进入优化器之前,在进程的早期阶段没有复制.这实际上是这一系列改进的下一次迭代.

  • 好的,我想我现在明白了.在这种情况下,移动语义是"更有效的副本",但仍然发生在临时构造的对象上.您是否有可能提供编译器无法忽视此类临时情况的示例?我认为在我的例子中它应该是可能的,但我不知道如何测试肯定. (2认同)