Programing

C ++ 17에서 도입 된 평가 순서 보증은 무엇입니까?

lottogame 2020. 10. 15. 07:23
반응형

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 순서로 평가됩니다.

  1. ab
  2. a-> b
  3. a-> * b
  4. a (b1, b2, b3)
  5. b @ = a
  6. a [b]
  7. a << b
  8. 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));

함수 호출 중에 여기에서 발생하는 네 가지 작업이 있습니다.

  1. new A
  2. unique_ptr<A> 건설자
  3. new B
  4. 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>

참고URL : https://stackoverflow.com/questions/38501587/what-are-the-evaluation-order-guarantees-introduced-by-c17

반응형