메서드 체인의 C ++ 실행 순서


108

이 프로그램의 출력 :

#include <iostream> 
class c1
{   
  public:
    c1& meth1(int* ar) {
      std::cout << "method 1" << std::endl;
      *ar = 1;
      return *this;
    }
    void meth2(int ar)
    {
      std::cout << "method 2:"<< ar << std::endl;
    }
};

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu).meth2(nu);
}

Is :

method 1
method 2:0

시작할 nu때 1 이 아닌 이유는 무엇 meth2()입니까?


41
@MartinBonner : 답을 알고 있지만, 어떤 의미 에서든 "명백한"이라고 부르지는 않을 것입니다 . 그렇다고하더라도 그것이 드라이브 바이 다운 투표를하는 적절한 이유가 아닙니다. 실망!
궤도의 경쾌함 경주

4
이것은 인수를 수정할 때 얻게되는 것입니다. 인수를 수정하는 함수는 읽기가 더 어렵고 다음 프로그래머가 코드 작업을 수행하는 데 예상치 못한 영향을 미치며 이와 같은 놀라움을 초래합니다. Invocant를 제외한 모든 매개 변수를 수정하지 않는 것이 좋습니다. 두 번째 메서드가 첫 번째 결과에 대해 호출되므로 그에 대한 효과가 정렬되기 때문에 invocant를 수정하는 것은 여기서 문제가되지 않습니다. 그래도 그렇지 않은 경우가 여전히 있습니다.
Jan Hudec 2016 년


@JanHudec 이것이 바로 함수형 프로그래밍이 함수 순도를 강조하는 이유입니다.
Pharap

2
예를 들어, 스택 기반의 호출 규칙은 아마 밀어 선호 nu, &nu그리고 c다음 호출, 그 순서대로 스택에 meth1다음 호출 스택에에 결과를 밀어 meth2레지스터 기반 호출 규칙이 원하는 것 동안, 로드 c&nu레지스터에, 호출 meth1, 레지스터에 로드 nu한 다음을 호출 meth2합니다.
Neil

답변:


66

평가 순서가 지정되지 않았기 때문입니다.

당신은 전에 평가받는 것을보고 있습니다 nu.main0meth1 호출된다. 이것이 연결 문제입니다. 하지 않는 것이 좋습니다.

멋지고 간단하고 명확하고 읽기 쉽고 이해하기 쉬운 프로그램을 만드십시오.

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu);
  c.meth2(nu);
}

14
이 문제를 해결하는 경우에 따라 평가 순서명확히 하는 제안 이 C ++ 17에 대해 나올 가능성이 있습니다
Revolver_Ocelot

7
메소드 체인과 같은 I (예는 <<출력 및 생성자에 너무 많은 인수 복잡한 객체에 대한 "개체 빌더"에 대한 - 그것은 출력 인수를 정말 심하게 혼합하지만.
마틴 보너 모니카 지원

34
내가이 권리를 이해하고 있습니까? meth1및의 평가 순서 meth2가 정의되어 있지만에 대한 매개 변수 평가 가 호출 meth2되기 전에 발생할 수 있습니다 meth1...?
Roddy

7
메서드 체이닝은 메서드가 합리적이고 호출자 만 수정하는 한 괜찮습니다 (첫 번째 메서드의 결과에 대해 두 번째 메서드가 호출되기 때문에 효과가 잘 정렬 된 경우).
Jan Hudec

4
생각할 때 논리적입니다. 그것은처럼 작동meth2(meth1(c, &nu), nu)
BartekChom

29

평가 순서에 관한 표준 초안 의이 부분 이 적절 하다고 생각합니다 .

1.9 프로그램 실행

...

  1. 언급 된 경우를 제외하고 개별 연산자의 피연산자 및 개별 식의 하위 식에 대한 평가는 순서가 지정되지 않습니다. 연산자의 피연산자의 값 계산은 연산자 결과의 값 계산 전에 순서가 지정됩니다. 스칼라 객체에 대한 부작용이 동일한 스칼라 객체에 대한 다른 부작용 또는 동일한 스칼라 객체의 값을 사용하는 값 ​​계산과 관련하여 순서가 지정되지 않고 잠재적으로 동시 적이 지 않은 경우 동작이 정의되지 않습니다.

그리고 또한:

5.2.2 함수 호출

...

  1. [참고 : 접미사 식과 인수의 평가는 모두 서로에 대해 순서가 지정되지 않습니다. 인수 평가의 모든 부작용은 함수가 입력되기 전에 순서가 지정됩니다 — 끝 참고]

따라서 라인의 c.meth1(&nu).meth2(nu);경우 최종 호출에 대한 함수 호출 연산자의 관점에서 연산자에서 무슨 일이 발생하는지 고려 meth2하여 접미사 표현식과 인수로의 분석을 명확하게 볼 수 있습니다 nu.

operator()(c.meth1(&nu).meth2, nu);

최종 함수 호출 (즉, 후위 식 및 ) 에 대한 후위 식 및 인수평가는 위 의 함수 호출 규칙에 따라 서로에 대해 순서 가 지정 되지 않습니다 . 따라서 스칼라 객체에 대한 접미사 표현식 계산의 부작용 은 함수 호출 이전 의 인수 평가와 관련하여 순서가 지정되지 않습니다. 위 의 프로그램 실행 규칙에 따르면 이것은 정의되지 않은 동작입니다.c.meth1(&nu).meth2nuarnumeth2

즉, 컴파일러가 호출 후 호출에 대한 nu인수 를 평가할 필요가 없습니다. 즉, meth2호출 meth1에 영향을 meth1미치는 부작용이 없다고 가정 할 수 있습니다 .nu . 평가에 .

위에서 생성 된 어셈블리 코드는 main함수에 다음 시퀀스를 포함합니다 .

  1. 변하기 쉬운 nu 는 스택에 할당되고 0으로 초기화됩니다.
  2. 레지스터 (ebx 제 경우)는 다음 값의 사본을받습니다.nu
  3. 의 주소 nuc 매개 변수 레지스터에로드됩니다.
  4. meth1 불린다
  5. 반환 값 레지스터와 이전에 캐시 된 값nu에서ebx 레지스터 파라미터 레지스터에로드
  6. meth2 불린다

중요하게도 위의 5 단계에서 컴파일러는 nu2 단계 의 캐시 된 값을 에 대한 함수 호출에서 재사용 할 수 있습니다 meth2. 여기서는 작동중인 '정의되지 않은 동작' nu호출에 의해 변경되었을 수 있는 가능성을 무시합니다 meth1.

참고 : 이 답변은 원래 형식에서 실질적으로 변경되었습니다. 마지막 함수 호출 전에 순서가 지정되지 않은 피연산자 계산의 부작용에 대한 초기 설명은 정확하지 않기 때문입니다. 문제는 피연산자 자체의 계산이 불확실하게 배열된다는 사실입니다.


2
이것은 잘못되었습니다. 함수 호출은 호출 함수에서 다른 평가를 사용하여 불확실하게 시퀀스됩니다 (시퀀스 이전 제약 조건이 달리 부과되지 않는 한). 인터리브하지 않습니다.
TC

1
@TC-인터리브되는 함수 호출에 대해 아무 말도하지 않았습니다. 나는 연산자의 부작용만을 언급했다. 위에서 생성 된 어셈블리 코드를 살펴보면 meth1이전 meth2에 실행되는 것을 볼 수 있지만 for 매개 변수 meth2는에 nu대한 호출 전에 레지스터 에 캐시 된 값입니다. meth1즉, 컴파일러가 잠재적 인 부작용을 무시했습니다. 내 대답과 일치합니다.
Smeeheey

1
당신은 "부작용 (즉, ar의 값을 설정하는 것)이 호출 전에 시퀀싱되는 것을 보장하지 않는다"고 정확히 주장하고 있습니다. 함수 호출에서 postfix-expression의 평가 (즉 c.meth1(&nu).meth2)와 해당 호출에 대한 인수 평가 ( nu)는 일반적으로 순서가 지정되지 않지만 1) 부작용은 모두 진입하기 전에 순서가 지정 meth2되고 2) c.meth1(&nu)함수 호출 이므로 ,에 대한 평가로 불확실하게 시퀀스가 ​​지정됩니다 nu. 내부 meth2에서에서 변수에 대한 포인터를 얻은 경우 main항상 1이 표시됩니다.
TC

2
"그러나 피연산자 계산의 부작용 (즉, ar 값 설정)이 위의 2에 따라) 어떤 것보다 먼저 시퀀싱된다는 보장은 없습니다." meth2인용하는 cppreference 페이지의 항목 3에서 언급했듯이 (올바르게 인용하는 것도 무시 했음)를 호출하기 전에 순서 가 지정됩니다.
TC

1
당신은 뭔가 잘못했고 더 악화 시켰습니다. 여기에는 정의되지 않은 동작이 전혀 없습니다. 예제를 지나서 [intro.execution] / 15를 계속 읽으십시오.
TC

9

1998 년 C ++ 표준, 섹션 5, para 4

언급 된 경우를 제외하고 개별 연산자의 피연산자 및 개별 식의 하위 식에 대한 평가 순서와 부작용이 발생하는 순서는 지정되지 않습니다. 이전 시퀀스 포인트와 다음 시퀀스 포인트 사이에서 스칼라 객체는 표현식 평가에 의해 최대 한 번 수정 된 저장된 값을 가져야합니다. 또한 이전 값은 저장할 값을 결정하기 위해서만 액세스해야합니다. 이 단락의 요구 사항은 전체 표현의 하위 표현의 허용 가능한 각 순서에 대해 충족되어야합니다. 그렇지 않으면 동작이 정의되지 않습니다.

(이 질문과 관련이없는 각주 # 53에 대한 참조는 생략했습니다.)

기본적으로 &nu호출하기 전에 평가해야 c1::meth1()하고, nu호출하기 전에 평가되어야한다 c1::meth2(). 그러나 nu이전에 평가해야하는 요구 사항은 없습니다 &nu(예 : nu먼저 평가 &nuc1::meth1()다음를 호출 한 다음 호출 하는 것이 허용됩니다. 이것이 컴파일러가 수행하는 작업 일 수 있음). 표현 *ar = 1c1::meth1()따라서 이전에 평가되는 것은 아닙니다 nu인은 main()에 전달하기 위해, 평가c1::meth2() .

이후의 C ++ 표준 (현재 내가 오늘 밤 사용하는 PC에는없는)은 본질적으로 동일한 절을 가지고 있습니다.


7

컴파일 할 때 meth1 및 meth2 함수가 실제로 호출되기 전에 매개 변수가 전달되었다고 생각합니다. "c.meth1 (& nu) .meth2 (nu);"를 사용할 때 nu = 0 값이 meth2에 전달되었으므로 "nu"가 후자 변경 되더라도 상관 없습니다.

당신은 이것을 시도 할 수 있습니다 :

#include <iostream> 
class c1
{
public:
    c1& meth1(int* ar) {
        std::cout << "method 1" << std::endl;
        *ar = 1;
        return *this;
    }
    void meth2(int* ar)
    {
        std::cout << "method 2:" << *ar << std::endl;
    }
};

int main()
{
    c1 c;
    int nu = 0;
    c.meth1(&nu).meth2(&nu);
    getchar();
}

그것은 당신이 원하는 답을 얻을 것입니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.