链接操作和对同一对象的评估顺序

时间:2014-02-19 19:23:14

标签: c++ c++11 standards chaining operator-precedence

考虑使用class MyClass

  • 修改了。的成员函数myClass& myFunction1(int) 对象并返回*this
  • 没有的成员函数int myFunction2() const 修改对象

C ++ 11/14标准是否保证:

myclass.myFunction1(myclass.myFunction2()).myFunction1(myclass.myFunction2());

相当于:

myclass.myFunction1(myclass.myFunction2());
myclass.myFunction1(myclass.myFunction2());

3 个答案:

答案 0 :(得分:3)

没有。编译器可以先调用myclass.myFunction2()两次,然后在第一个示例代码中执行两个myFunction1调用。但不是在第二个示例代码中。

没有什么可以阻止编译器在函数参数的评估和函数的调用之间插入某些东西。只要调用实际发生在评估调用参数(该函数)之后。用Standardese术语

  

当调用函数时(无论函数是否为内联函数),在执行每个表达式或语句之前,对与任何参数表达式或指定被调用函数的后缀表达式相关联的每个值计算和副作用进行排序。被调用函数的主体。

不同的表达通常是没有排序的(除非有明确的措辞对它们进行排序)。您对myclass.myFunction2的两次调用是如此无效的情况,因此对myclass.myFunction2的一次调用可能会出现在另一次调用之后(以及调用任何myFunction1之前)。

答案 1 :(得分:1)

  

任何C ++运算符的操作数的评估顺序,包括函数调用表达式中函数参数的评估顺序,以及任何表达式中子表达式的评估顺序都是未指定的(除非另有说明)。编译器将以任何顺序对它们进行评估,并且可以在再次评估相同的表达式时选择另一个顺序。   在C ++中没有从左到右或从右到左评估的概念,不要混淆运算符的从左到右和从右到左的关联性:表达式f1()+ f2( )+ f3()由于operator +的从左到右的关联性被解析为(f1()+ f2())+ f3(),但是对f3的函数调用可以在f1()之前或之间进行评估。或者在运行时使用f2()。

对于试图用波兰表示法编写表达式并应用c ++运算符here规则的任何人

precedence所述

答案 2 :(得分:0)

正如其他人所指出的,对myFunction2()的调用相对于彼此是无序的。所以它不一样。

为了让他们订购,我们可能会这样做:

template<typename T>
struct continuation {
  T&& t;
  continuation(T&& t_):t(std::forward<T>(t_)) {}
  continuation( continuation&& ) = delete;
  continuation( continuation const& ) = delete;
  continuation() = delete;
  void operator=(continuation const&) = delete;
  void operator=(continuation &&) = delete;
  template<typename Lambda>
  continuation<T> then( Lambda&& closure ) && {
    std::forward<Lambda>(closure)( std::forward<T>(t) );
    return { std::forward<T>(t) }; // two forwards!  Dangerous
  }
};
struct myClass {
  continuation<myClass&> myFunction1(int) &{ return {*this}; }
  continuation<myClass> myFunction1(int) &&{ return {*this}; }
  int myFunction2() const { return 7; }
};
int main() {
  myClass c;
  c.myFunction1( c.myFunction2() ).then( [](auto&& c) { c.myFunction1( c.myFunction2() ); } );
}

将是一个代码示例,它允许您使用相同的操作顺序在一行上链接您对类实例的使用(在C ++ 1y中)。

但是,继续样式then调用传统上用于将来的返回值,而不是对this的链调用的有序评估。

所有嵌套括号都会让人感到烦恼。解决 问题的一种方法是使用命名运算符*then*,这需要更多样板文件,但是为您提供:

int main() {
  myClass c;
  c.myFunction1( c.myFunction2() )
  *then* [](auto&& c) { c.myFunction1( c.myFunction2() ); }
  *then* [](auto&& c) { c.myFunction1( c.myFunction2() ); };
}

看起来很漂亮。

所有这些基本上都是避免创建简单别名的方法。

myClass get_my_class() { return {} };
int main() {
  auto&& c = get_my_class();
  c.myFunction1(c.myFunction2());
  c.myFunction1(c.myFunction2());
  c.myFunction1(c.myFunction2());
}

两者都不需要任何上述体操,并且至少同样有效。如果某个对象有一个复杂的名称,并且你想避免再次使用它,那么使用引用除了一些极端情况外都可以使用。

特别是角落案例是一个函数,它返回对生命周期结束于当前行末尾的对象的引用。这通常是不好的做法:至少使用易于移动的对象,您应该返回一个临时对象。

但是,可能会发生不可避免的情况。考虑创建一个链接操作的简单lambda或函数,并在对象所在的单行上调用它,而不是方法链接或延续或其他。或者,断开该行并通过其原始化身的参考扩展来存储对象的生命周期。