C ++ 17에서 도입 된 평가 순서 보증은 무엇입니까?
일반적인 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;
아직 정의되지 않았습니다.
인터리빙은 C ++ 17에서 금지됩니다.
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) 이전에 발생하는 모든 주문은 이제 금지됩니다.
식 평가 순서에 대한 몇 가지 참고 사항을 찾았습니다.
- 빠른 Q : 왜 C ++에는 함수 인수를 평가하기위한 지정된 순서가 없습니까?
일부 평가 순서는 오버로드 된 연산자와 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>
'Programing' 카테고리의 다른 글
vector :: at 대 vector :: operator [] (0) | 2020.10.15 |
---|---|
신호 강도를 사용하여 Wi-Fi 라우터와의 거리를 계산하는 방법은 무엇입니까? (0) | 2020.10.15 |
XML-RPC와 SOAP의 차이점은 무엇입니까? (0) | 2020.10.15 |
Amazon EC2에서 HTTPS 설정 (0) | 2020.10.15 |
벡터를 목록으로 변환하고 벡터의 각 요소를 목록의 요소로 변환합니다. (0) | 2020.10.15 |