【问题标题】:Is this code well defined?这段代码是否定义良好?
【发布时间】:2011-10-04 23:14:57
【问题描述】:

我怀疑根据 C++ 标准(假设 C++0x),以下函数链接会导致未指定的序列。只是想要一个确认,如果有人可以提供解释,我将不胜感激。

#include <iostream>

struct TFoo 
{
    TFoo(int) 
    {
        std::cout<<"TFoo"<<std::endl;
    };
    TFoo foobar1(int) 
    {
        std::cout<<"foobar1"<<std::endl;
        return *this;
    };
    TFoo foobar2(int) 
    {
        std::cout<<"foobar2"<<std::endl;
        return *this;
    };
    static int bar1() 
    {
        std::cout<<"bar1"<<std::endl;
        return 0;
    };
    static int bar2() 
    {
        std::cout<<"bar2"<<std::endl;
        return 0;
    };
    static int bar3()
    {
        std::cout<<"bar3"<<std::endl;
        return 0;
    }
};

int main(int argc, char *argv[])
{
    // is the sequence well defined for bar1, bar2 and bar3?
    TFoo(TFoo::bar1()).foobar1(TFoo::bar2()).foobar2(TFoo::bar3());
}

* 编辑:删除函数的 __fastcall 说明符(不需要/与问题相关)。

【问题讨论】:

  • 我并不是要给人吹毛求疵,但从技术上讲,__fastcall 不是 C++ 规范的一部分,所以我认为规范对此没有什么要说的。你有什么理由把它包括在这里吗?或者这个问题具体是关于__fastcall 与序列点的交互?

标签: c++ c++11 sequence-points operator-precedence


【解决方案1】:

未指定评估顺序。草案C++0x spec的相关部分是1.9,第14和15段:

14 与 full-expression 关联的每个值计算和副作用都在与要评估的下一个 full-expression 关联的每个值计算和副作用之前进行排序。

15 除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的求值是无序的。

这里相关的full-expression是:

TFoo(TFoo::bar1()).foobar1(TFoo::bar2()).foobar2(TFoo::bar3());

因此对其子表达式的求值是无序的(除非在某处我遗漏了一个异常)。

我很确定早期的标准包括具有相同效果的语言,但在“序列点”方面。

[编辑]

第 15 段还说:

调用函数时(无论函数是否内联),与任何参数表达式或指定被调用函数的后缀表达式相关的每个值计算和副作用,都在执行每个表达式或语句之前排序被调用函数的主体。 [注意:与不同参数表达式相关的值计算和副作用是无序的。- 尾注]

“指定被调用函数的后缀表达式”类似于foo().bar() 中的foo().bar

这里的“注释”只是澄清了参数评估顺序不是“未指定顺序”默认值的例外。通过推断,评估顺序也与“指定被调用函数的后缀表达式”相关联;或者,如果您愿意,可以使用 this 参数的表达式的求值顺序。 (如果有异常,这将是指定它的自然位置。或者可能是第 5.2.2 节讨论函数调用。这两个部分都没有说明此示例的评估顺序,因此未指定。)

【讨论】:

  • @Zach:确实。但是,如果您打算依赖评估顺序,则需要找到设置顺序的特定规则。 C/C++ 一直没有明确指定这一点,以便给编译器和 CPU 重新排序以提高性能的空间。
  • IMO 值得一提的是 foobar1foobar2 之前被调用。所以顺序是受限制的(因为foobar1() 的结果需要在用作operator. 的操作数之前进行评估,1.9p15),尽管相对于其他调用大多未指定。
【解决方案2】:

是的,函数参数的求值顺序未指定。

对我来说,linux 上的 gcc 4.5.2 产生

bar3
bar2
bar1
TFoo
foobar1
foobar2

但是 linux 上的 clang++ 和 solaris 上的 gcc 3.4.6 产生

bar1
TFoo
bar2
foobar1
bar3
foobar2

分析一个更简单的例子,TFoo(0).foobar1(TFoo::bar2()); 是对TFoo::foobar1 的调用,它接受两个参数:子表达式TFoo(0) 的结果(作为隐藏参数this)和子表达式Tfoo::bar2() 的结果.对我来说,gcc先执行bar2(),然后是TFoo的构造函数,然后调用foobar1(),而例如clang++,先执行TFoo的构造函数,然后是bar2(),然后调用foobar1()

【讨论】:

  • 我知道函数参数的求值顺序是未指定的,但在这种情况下,它实际上是每个有问题的链式函数(指定了它的顺序)的函数参数(单数)的 eval 顺序.有什么相关标准可以说这是真正未指定的吗?
  • @Zach Saw:成员函数也将this 作为隐含参数。
  • 想详细说明一下?这是否意味着链式函数(foobar1 和 foobar2)的顺序也未指定?
  • @Zach Saw:不,foobar2 将始终在 foobar1 之后调用,因为 foobar1 的结果是 foobar2 的参数。在计算完所有参数后启动函数调用。
  • PS:这是非正式的,当然,“this 参数”的标准术语是@Nemo 发布的“后缀子表达式”。为了一个真实的例子而保留答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-01
  • 2020-06-04
  • 1970-01-01
  • 1970-01-01
  • 2012-10-30
  • 2015-09-16
相关资源
最近更新 更多