2

考虑到 C++ 编译器在实例化临时对象和调用返回值优化等机制方面的自由度,通过查看某些代码是否会调用移动或复制语义(或调用多少)并不总是很清楚。

几乎感觉这些原语似乎是为了附带优化而存在的。也就是说,您可能会或可能不会得到它们。当很难控制招式本身的调用时,似乎很难设计任何一种利用招式的资源管理策略。

有没有办法清楚地(简单地)预测某些代码中可能出现的位置和数量?理想情况下,不需要成为编译器内部专家就可以做到这一点。

4

1 回答 1

1

当很难控制招式本身的调用时,似乎很难设计任何一种利用招式的资源管理策略。

我会在这里反驳。在设计资源处理类时利用移动语义应该独立于客户端代码中复制或移动构造如何或何时发生。一旦有了 move-ctor/assignment,就可以设计客户端代码来利用这些特殊成员函数的存在。

有没有办法清楚地(简单地)预测某些代码中可能出现的位置和数量?

有点难以说出这里的简单含义,但这就是我的理解:

  • 鉴于一个类没有移动 ctor/赋值运算符,您将始终获得一份副本。这是微不足道的,但在使用具有用户定义的析构函数和/或复制ctor/赋值的遗留代码中的类时要记住这一点,因为在这种情况下编译器不会生成移动ctor/赋值。

  • 返回值优化。该问题被标记为 C++11,因此您不能保证使用 C++17 带来的纯右值进行初始化时复制省略。但是,可以公平地假设您的编译器已经实现了相同的机制。因此,

    struct A {};
    
    A func() { return A{}; }
    

    可以假设A在调用端构造函数返回值绑定到的实例。这既不会导致移动也不会导致复制构造。如果返回的对象有名称,则可以乐观地假设相同的行为,只要func()没有导致 NRVO 不可能的分支。

    作为本指南的一个例外,作为函数参数的函数返回值不符合返回值优化的条件。因此,在 A 是可移动构造的情况下,移动/转发它们以防止复制:

    A func(A& a) { return std::move(a); }
    

    因此,由 的返回值创建的对象func(A&)将被移动构造。

  • 函数参数本身并不显示它们的行为方式,它取决于类型及其特殊的成员函数。给定

    void f1(A a1) { A a2{std::move(a1)}; };
    void f2(A& a1) { /* Same as above. */ };
    void f1(A&& a1) { /* Again, same. */ };
    

    如果具有移动 ctor,则实例a2是移动构造的,否则,它是副本。A

除了上面的示例案例之外,还有很多要发现的东西,我既无法详细说明,也无法满足所需的简单答案。此外,当您不知道您正在处理的类型时,情况会有所不同,例如在函数或类模板中。在这种情况下,关于如何处理是否进行复制或移动的相关不确定性的好读物是 Eff 中的第 29 条。现代 C++(“假设移动操作不存在、不便宜且未使用”)。

于 2018-08-24T07:17:09.853 回答