일반적인 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());
답변:
지금까지 평가 순서가 지정되지 않은 몇 가지 일반적인 경우 는에서 지정되고 유효합니다 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 ++ Programming Language , 4th edition, 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 순서로 평가됩니다.
- ab
- 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()
다른 원시 포인터가 스마트 포인터 매개 변수에 연결되기 전에 호출 중 하나 가 throw 되면 누출됩니다 .
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에서 새로운 규칙은 인터리빙을 금지합니다. [intro.execution]에서 :
각 함수 호출 F에 대해 F 내에서 발생하는 모든 평가 A 및 F 내에서 발생하지 않지만 동일한 스레드에서 평가되고 동일한 신호 처리기 (있는 경우)의 일부로 평가되는 모든 평가 B에 대해 A가 B보다 먼저 시퀀싱됩니다. 또는 B가 A보다 먼저 시퀀싱됩니다.
그 문장에는 다음과 같은 각주가 있습니다.
즉, 함수 실행은 서로 인터리브되지 않습니다.
이로 인해 (1), (2), (3), (4) 또는 (3), (4), (1), (2)의 두 가지 유효한 순서가 남습니다. 어떤 순서를 취하는지는 명시되어 있지 않지만 둘 다 안전합니다. (1) (3) 둘 다 (2)와 (4) 이전에 발생하는 모든 주문은 이제 금지됩니다.
식 평가 순서에 대한 몇 가지 참고 사항을 찾았습니다.
일부 평가 순서는 오버로드 된 연산자와 C ++ 17에 추가 된 완전한 인수 규칙을 보장합니다. 그러나 어떤 주장이 먼저 진행되는지는 명시되지 않은 채로 남아 있습니다. C ++ 17에서는 호출 할 항목을 제공하는 표현식 ((함수 호출의 왼쪽에있는 코드)이 인수 앞에오고 먼저 평가되는 인수가 다음 인수가되기 전에 완전히 평가되도록 지정되었습니다. 시작되고 객체 메소드의 경우 객체의 값이 메소드에 대한 인수보다 먼저 평가됩니다.
21) 괄호로 묶인 이니셜 라이저에서 쉼표로 구분 된 표현식 목록의 모든 표현식은 함수 호출 ( indeterminately-sequenced ) 처럼 평가됩니다.
C ++ 언어는 함수 호출에 대한 인수가 평가되는 순서를 보장하지 않습니다.
에서 관용적 C에 대한 식 평가 순서 P0145R3.Refining ++ 나는 발견했습니다 :
postfix-expression의 값 계산 및 관련 부작용은 expression-list의 표현식보다 먼저 시퀀싱됩니다. 선언 된 매개 변수의 초기화는 인터리빙없이 불확실하게 시퀀스 됩니다.
그러나 나는 그것을 표준에서 찾지 못했습니다. 대신 표준에서 찾았습니다.
6.8.1.8 순차 실행 [intro.execution] 모든 값 계산 및 표현식 X와 관련된 모든 부작용이 모든 값 계산 및 표현식 Y와 관련된 모든 부작용 이전에 시퀀싱되는 경우 표현식 X는 표현식 Y 앞에 시퀀싱된다고합니다. .
6.8.1.9 순차 실행 [intro.execution] 전체 표현식과 관련된 모든 값 계산 및 부작용은 평가할 다음 전체 표현식과 관련된 모든 값 계산 및 부작용 이전에 시퀀싱됩니다.
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>