在典型的C ++代码中,C ++ 17评估顺序保证(P0145)中投票的含义是什么?
如下所示,它有什么变化?
i = 1;
f(i++, i)
和
std::cout << f() << f() << f();
要么
f(g(), h(), j());
在典型的C ++代码中,C ++ 17评估顺序保证(P0145)中投票的含义是什么?
如下所示,它有什么变化?
i = 1;
f(i++, i)
和
std::cout << f() << f() << f();
要么
f(g(), h(), j());
Answers:
到目前为止,尚未指定评估顺序的一些常见情况已通过指定并有效C++17
。现在,未指定一些未定义的行为。
i = 1;
f(i++, i)
尚未定义,但现在未指定。具体而言,未指定的是每个参数f
相对于其他参数的求值顺序。i++
可能在之前评估i
,反之亦然 实际上,尽管在同一编译器下,但它可能以不同的顺序评估第二个调用。
但是,在执行任何其他参数之前,必须对每个参数的求值具有所有副作用的完整执行。因此,您可能会得到f(1, 1)
(首先评估第二个参数)或f(1, 2)
(首先评估第一个参数)。但是,您将永远不会得到f(2, 2)
这种性质的东西。
std::cout << f() << f() << f();
未指定,但它将与运算符优先级兼容,因此对的第一次评估f
将在流中排在第一位(下面的示例)。
f(g(), h(), j());
仍具有g,h和j的未指定评估顺序。请注意,对于getf()(g(),h(),j())
,规则状态getf()
将在之前进行评估g, h, j
。
还请注意提案文本中的以下示例:
std::string s = "but I have heard it works even if you don't believe in it"
s.replace(0, 4, "").replace(s.find("even"), 4, "only")
.replace(s.find(" don't"), 6, "");
该示例来自The C ++编程语言,第四版,Stroustrup,并且以前是未指定的行为,但是对于C ++ 17,它将按预期运行。可恢复功能(.then( . . . )
)也存在类似问题。
作为另一个示例,请考虑以下内容:
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
struct Speaker{
int i =0;
Speaker(std::vector<std::string> words) :words(words) {}
std::vector<std::string> words;
std::string operator()(){
assert(words.size()>0);
if(i==words.size()) i=0;
// Pre-C++17 version:
auto word = words[i] + (i+1==words.size()?"\n":",");
++i;
return word;
// Still not possible with C++17:
// return words[i++] + (i==words.size()?"\n":",");
}
};
int main() {
auto spk = Speaker{{"All", "Work", "and", "no", "play"}};
std::cout << spk() << spk() << spk() << spk() << spk() ;
}
使用C ++ 14之前,我们可能(并且将)获得诸如
play
no,and,Work,All,
代替
All,work,and,no,play
请注意,上述内容实际上与
(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;
但是,仍然没有保证在C ++ 17之前,第一个调用将首先进入流中。
参考:从接受的提案:
后缀表达式从左到右评估。这包括函数调用和成员选择表达式。
赋值表达式从右到左求值。这包括复合作业。
移位运算符的操作数从左到右评估。总而言之,以下表达式将按a,b,c,d的顺序求值:
- b
- a-> b
- a-> * b
- a(b1,b2,b3)
- b @ = a
- a [b]
- a << b
- a >> b
此外,我们建议以下附加规则:涉及重载运算符的表达式的求值顺序由与相应内置运算符关联的顺序决定,而不是由函数调用规则决定。
编辑说明:我的原始答案被误解了a(b1, b2, b3)
。的顺序b1
,b2
,b3
仍然是不确定的。(感谢@KABoissonneault,所有评论者。)
然而,(如@Yakk指出),这是非常重要的:即使b1
,b2
,b3
是不平凡的表情,他们每个人都完全评估,并绑在各自的功能参数其他的人都开始进行评估之前。该标准规定如下:
§5.2.2-函数调用5.2.2.4:
。。。postfix-expression在expression-list中的每个表达式和任何默认参数之前进行排序。与参数的初始化相关联的每个值计算和副作用以及初始化本身在与任何后续参数的初始化相关联的每个值计算和副作用之前进行排序。
但是,GitHub草稿中缺少其中一个新句子:
与参数的初始化相关联的每个值计算和副作用以及初始化本身在与任何后续参数的初始化相关联的每个值计算和副作用之前进行排序。
这个例子是在那里。它解决了数十年来存在的异常安全问题(如Herb Sutter所述),
f(std::unique_ptr<A> a, std::unique_ptr<B> b);
f(get_raw_a(), get_raw_a());
如果其中一个调用get_raw_a()
在另一个原始指针与其智能指针参数绑定之前抛出,则将泄漏。
正如TC指出的那样,该示例存在缺陷,因为原始指针的unique_ptr构造是显式的,从而阻止了该示例的编译。
还要注意这个经典问题(标记为C,不是C ++):
int x=0;
x++ + ++x;
仍未定义。
a
,然后b
,然后c
,然后d
”然后显示的a(b1, b2, b3)
,这表明所有b
表达式不一定按任何顺序求值(否则,将是a(b, c, d)
)
a(b1()(), b2()())
可以订购b1()()
,并b2()()
以任意顺序,但它不能做的b1()
,然后b2()()
再b1()()
:它可能不再交错他们处决。简而言之,“ 8。功能调用的替代评估命令”是已批准的更改的一部分。
f(i++, i)
未定义。现在未指定。Stroustrup的字符串示例可能未指定,但未定义。f(get_raw_a(),get_raw_a());不会编译,因为相关的unique_ptr
构造函数是显式的。最后,x++ + ++x
是未定义的时期。
在C ++ 14中,以下内容是不安全的:
void foo(std::unique_ptr<A>, std::unique_ptr<B>);
foo(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));
在函数调用期间,这里发生了四种操作
new A
unique_ptr<A>
建设者new B
unique_ptr<B>
建设者这些的顺序是完全不确定的,因此一个完全有效的顺序是(1),(3),(2),(4)。如果选择了此顺序并抛出(3),则(1)中的内存会泄漏-我们尚未运行(2),这可以防止泄漏。
在C ++ 17中,新规则禁止交织。来自[介绍执行]:
对于每个函数调用F,对于在F内发生的每个求值A和在F中不发生但在同一线程上并且作为同一信号处理程序(如果有)的一部分进行求值的每个求值B,A都在B之前排序。或B在A之前排序。
该句子的脚注如下:
换句话说,函数执行不会相互交织。
这给我们留下了两个有效的顺序:(1),(2),(3),(4)或(3),(4),(1),(2)。尚不确定采用哪种订购方式,但两者都是安全的。现在禁止所有在(2)和(4)之前都出现(1)(3)的命令。
我发现了一些有关表达式求值顺序的注意事项:
某些评估顺序可以保证在C ++ 17中添加重载运算符和完整参数规则。但是,仍然没有确定先争论哪个。在C ++ 17中,现在指定了给出要调用的内容的表达式((函数调用的)左侧的代码在参数之前,并且首先计算的哪个参数在下一个参数之前被完全求值。开始,对于对象方法,在计算方法参数之前先评估对象的值。
21)用括号分隔的初始值设定项中用逗号分隔的表达式列表中的每个表达式都像对函数调用那样进行评估(不确定地排序)
C ++语言不保证对函数调用的参数进行评估的顺序。
在P0145R3中,为惯用C ++定义表达式评估顺序时,我发现:
后缀表达式的值计算和相关的副作用在表达式列表中的表达式之前进行排序。声明的参数的初始化不确定地排序,没有交织。
但是我没有在标准中找到它,而是在标准中找到了:
6.8.1.8顺序执行[简介执行] 如果在每次值计算和与表达式Y相关的每个副作用之前对每个值计算和与该表达式X相关的每个副作用进行排序,表达式X称为在表达式Y之前排序。 。
6.8.1.9顺序执行[序言执行] 在与要评估的下一个完整表达式关联的每个值计算和副作用之前,对与一个完整表达式关联的每个值计算和副作用进行排序。
7.6.19.1逗号运算符[expr.comma] 一对用逗号分隔的表达式从左到右求值; ...
因此,我比较了三种针对14和17标准的编译器的行为。探索的代码是:
#include <iostream>
struct A
{
A& addInt(int i)
{
std::cout << "add int: " << i << "\n";
return *this;
}
A& addFloat(float i)
{
std::cout << "add float: " << i << "\n";
return *this;
}
};
int computeInt()
{
std::cout << "compute int\n";
return 0;
}
float computeFloat()
{
std::cout << "compute float\n";
return 1.0f;
}
void compute(float, int)
{
std::cout << "compute\n";
}
int main()
{
A a;
a.addFloat(computeFloat()).addInt(computeInt());
std::cout << "Function call:\n";
compute(computeFloat(), computeInt());
}
结果(更一致的是clang):
<style type="text/css">
.tg {
border-collapse: collapse;
border-spacing: 0;
border-color: #aaa;
}
.tg td {
font-family: Arial, sans-serif;
font-size: 14px;
padding: 10px 5px;
border-style: solid;
border-width: 1px;
overflow: hidden;
word-break: normal;
border-color: #aaa;
color: #333;
background-color: #fff;
}
.tg th {
font-family: Arial, sans-serif;
font-size: 14px;
font-weight: normal;
padding: 10px 5px;
border-style: solid;
border-width: 1px;
overflow: hidden;
word-break: normal;
border-color: #aaa;
color: #fff;
background-color: #f38630;
}
.tg .tg-0pky {
border-color: inherit;
text-align: left;
vertical-align: top
}
.tg .tg-fymr {
font-weight: bold;
border-color: inherit;
text-align: left;
vertical-align: top
}
</style>
<table class="tg">
<tr>
<th class="tg-0pky"></th>
<th class="tg-fymr">C++14</th>
<th class="tg-fymr">C++17</th>
</tr>
<tr>
<td class="tg-fymr"><br>gcc 9.0.1<br></td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
</tr>
<tr>
<td class="tg-fymr">clang 9</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td>
</tr>
<tr>
<td class="tg-fymr">msvs 2017</td>
<td class="tg-0pky">compute int<br>compute float<br>add float: 1<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
</tr>
</table>