【问题标题】:Understanding eliding rules with regard to c++11了解有关 c++11 的省略规则
【发布时间】:2012-02-13 20:41:02
【问题描述】:

我一直在使用右值引用和移动语义进行测试,并希望确保我了解何时应该删除副本以及何时应该遵循移动语义。

鉴于以下

class NRVCA
{
public:
    NRVCA(int x):
    {}
    NRVCA(const NRVCA & Rhs)
    {}    
    NRVCA& operator=(const NRVCA& dref)
    {}          
};

NVCRA GetATemp()
{       
   return NVCRA(5);
} 

NVCRA GetACopy()
{
   NVCRA ret(5);
   ...
   return ret;  
}

int main()
{ 
    //This call will be elided allays and invoke the single param constructor 
    NVCRA A = GetATemp();
    //This call will be a traditional copy the complier may elide this 
    // if so the work will be done inline 
    NVCRA B = GetACopy();

}

在这种情况下,移动语义不起作用,与 c++11 中的 C++03 的唯一区别是编译器不允许省略它们,而是要求它们省略。

所以 问题 1. 在什么情况下我可以保证复制构造函数会或不会被省略。

问题2.有没有办法强制编译器不省略。

问题 3. 假设您有逻辑上一致的复制操作,我不希望编译器这样做有什么合乎逻辑的原因吗?

问题 4. 如果我定义了一个移动构造函数,那么在无论如何都没有删除副本的情况下会发生移动。这是否会影响我的课程设计。

【问题讨论】:

  • A 是什么类型?应该是NRVCA

标签: c++ c++11


【解决方案1】:
  1. 删除副本和移动始终是可选优化,标准不保证何时完成。省略不同于编译器选择移动而不是复制。该语言保证何时根据其正常的重载解决规则选择移动构造或移动分配。

  2. 一些编译器提供一个标志来关闭省略。 gcc 和 clang 有-fno-elide-constructors。 MSVC没有具体的选项,但是禁用优化可以避免一些省略(但有些无论如何也不能关闭,比如Foo x = 1;中的复制)

  3. 我不知道有什么理由不在生产版本中省略副本/移动。

  4. 有些人建议在返回“重”类时不要依赖“返回值优化”,因为不能保证 RVO。就我个人而言,我只是验证了我的编译器对此很满意,并继续使用它。现在可以移动对象,您不再需要担心编译器是否支持此类优化,因为即使不支持,您仍然会得到移动而不是副本。

【讨论】:

  • 请注意,如果对象是“扁平的”,即没有指针,则移动而不是复制不会给您带来太多好处。然后你会得到一个普通的旧副本。
  • 是的,正如 Herb Sutter 所说,将移动视为对副本的优化。对于不会比复制更快的类型,甚至实现移动 ctor 或移动赋值运算符也是没有意义的。这意味着移动实际上只适用于“重”物体,这与人们以前可能不得不依赖 RVO 的地方相同。
【解决方案2】:

引用 C++11 标准 (12.8/31),“当满足某些条件时,实现允许省略类对象的复制/移动构造,即使复制/move 对象的构造函数和/或析构函数有副作用。”,所以:

  1. 不保证复制省略。 (技术上;但出于营销原因,编译器供应商会这样做)
  2. 这是一个特定于编译器的问题。
  3. 技术上存在一些情况 (see here),但它们会表明 (IMO) 设计中存在一些错误。
  4. 您仍然应该提供移动构造函数(如果可以有效地实现它)。在某些情况下,禁止复制省略,但移动构造函数就可以了:

    vector<string> reverse(vector<string> vec)
    {
        reverse( vec.begin(), vec.end() );
        return vec;
    }
    
    auto vec = reverse(vector<string>{"abc", "def", "ghi"});
    

复制省略明确不允许从函数参数传播到从临时初始化的最终自动值。但是由于移动构造函数没有调用副本。


为了更具体的说我最后一句话,我引用了N3290,现在很难得到,但它非常接近N3337

12.8/31:

当满足某些条件时,允许实现省略类的复制/移动构造 对象,即使对象的复制/移动构造函数和/或析构函数有副作用。在这种情况下, 该实现将省略的复制/移动操作的源和目标视为简单的两个不同 引用同一对象的方式,并且该对象的销毁发生在较晚的时间 当两个对象在没有优化的情况下被销毁时。这种复制/移动的省略 称为复制省略的操作在以下情况下是允许的(可以组合到 消除多个副本):

-- 在具有类返回类型的函数的 return 语句中,当表达式是 a 的名称时 具有相同 cv-unqualified 的非易失性自动对象(函数或 catch 子句参数除外) type 作为函数返回类型,可以通过构造省略复制/移动操作 自动对象直接转化为函数的返回值

12.8/32:

当满足省略复制操作的标准时首先执行为副本选择构造函数的重载决策,就好像对象是由右值指定的一样。如果重载决议失败,或者如果所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能是 cv 限定的),则再次执行重载决议,将对象视为左值。 [...]

【讨论】:

  • 你能举出从参数到返回值的省略副本是不允许的吗?我听说一些事情似乎表明它是允许的,只是没有人这样做,因为实施起来不切实际。
  • 我添加了引文。另一方面,我永远无法理解实施这种省略会有什么困难。你碰巧知道吗?
  • 请注意,即使不允许复制省略,在 as-if 规则下仍可能允许优化复制或移动。当然在这种情况下,编译器必须证明删除不会改变可观察的行为,但我猜移动构造函数这样做的机会比复制构造函数要高。
  • @Andrzej 谢谢。我认为问题在于构造函数参数的代码需要知道将发生省略,以便它可以在返回值的位置构造该参数。该信息是函数内部的,而参数是由函数的调用者构造的。因此,如果调用者可以看到它可能会省略一个副本(例如,因为它可以看到函数定义),那么函数实现将不需要执行该副本。
  • @Andrzej 但是,如果函数无法确定它可以忽略该副本(例如,因为它位于不同的 TU 中而无法访问函数定义),则函数实现将需要执行该操作显式复制。
【解决方案3】:
  1. 我不相信编译器何时可以选择省略副本,即使复制构造函数有副作用。这是标准明确允许的。

  2. 哪个编译器?例如,我相信您可以关闭优化,而不是在 g++ 中进行省略。 没有办法在语言中强制任何编译器在它认为可以省略的地方生成一个复制构造函数。即使复制构造函数有副作用,该标准也明确允许省略。

  3. 我想不出不逃避的理由。

  4. 作为一般规则,复制省略不应影响类的逻辑设计。首先设计您的类,然后如果您遇到性能问题,请使用分析器来帮助改进它。

【讨论】:

  • for 2 我的意思是用语言或某种约定向编译器表明我有一个具有副作用的复制构造函数,我需要复制它。这不是正常情况。
  • 不,对于那些你的 copy-ctor 有副作用的情况,没有标准的方法来禁用特定类型的省略。 C++ 在允许省略时明确不考虑副作用。您应该始终编写您的 copy-ctor,使其在存在省略的情况下表现良好,否则您可能会得到未定义的行为。基本上,除了复制和移动之外,不要使用复制或移动构造函数。
猜你喜欢
  • 2011-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多