나는 NRVO에 관해 읽고 있었고, 언제 NRVO에 의존해야하는지 이해하려고 노력했다. 이제 나는 질문이 있습니다. 왜 NRVO에 의존해야합니까? 반환 매개 변수를 참조로 명시 적으로 전달할 수 있으므로 대신 NRVO에 의존할만한 이유가 있습니까?왜 이름 지정된 반환 값 최적화에 의존해야합니까?
답변
참조 값을 처리하는 것은 참조 매개 변수에 쓰는 방법으로 처리하는 것보다 간단합니다.
C GetByRet() { ... }
void GetByParam(C& returnValue) { ... }
첫 번째 문제는 그것이 체인 방법 불가능 그것은 또한 사용하기 auto
등의 기능을 불가능하게
Method(GetByRet());
// vs.
C temp;
GetByParam(temp);
Method(temp);
를 호출 할 수 있다는 것입니다 다음과 같은 두 가지 방법을 고려하십시오. 또한 std::map<std::string, std::list<std::string>*>
auto ret = GetByRet();
// vs.
auto value; // Error!
GetByParam(value);
같은 유형의 더 중요한 C
하지만 같은 유형의 문제를 많이하지 GMacNickG가 지적했듯이, 어떤 종류 C
정상적인 코드는 사용할 수 없습니다 개인 생성자가있는 경우? 어쩌면 생성자가 private
이거나 기본 생성자가 없을 수도 있습니다. 다시 한번 GetByRet
챔피언 같은 작품과 GetByParam
는
C ret = GetByRet(); // Score!
// vs.
C temp; // Error! Can't access the constructor
GetByParam(temp);
또한 일부 유형은 기본값으로 생성 될 수 없습니다. (그리고 * 가능하다면 쓰레기 인자로 초기화하는 것은 시간 낭비 일 것입니다.) – GManNickG
+1, 또 다른 포인트는 다음과 같습니다 :'typedef std :: vector
체인 및 기본 생성자 사용 가능성이있는 좋은 예입니다. 고맙습니다. –
이 많은 함수에 const가 아닌 참조 매개 변수를 전달하고 함수에서 이러한 매개 변수를 변경하는 것은 매우 직관적 아니라고 주장 실패합니다.
또한 값별로 결과를 반환하는 많은 사전 정의 된 연산자가 있습니다 (예 : operator+
, operator-
등과 같은 산술 연산자). 이러한 연산자의 기본 의미 (및 서명)를 유지하려는 경우 NRVO를 사용하여 값에 의해 반환되는 임시 개체를 최적화해야합니다.
마지막으로, 값으로 반환하면 비 const 참조 (또는 포인터)에 의해 변경 될 매개 변수를 전달하는 것보다 많은 경우에 더 쉽게 체인을 허용합니다.
연산자와 좋은 예, 감사합니다. –
참조로 항상 값을 반환 할 수는 없습니다 (기본 카운터 예제로 operator+
을 생각해보십시오). 가 의존 또는 NRVO 항상 발생할 것으로 예상 당신은 일반적으로하지 않습니다,하지만 당신은 컴파일러가 최적화의 합리적인 일을 예상 할 :
some function could be optimized just by using return parameter
에 대한 편집 :
먼저, 함수가 자주 호출되지 않습니다, 또는 컴파일러가 충분한 현명함이있는 경우 그 수익으로 아웃 - 매개 변수가 최적화입니다 보장 할 수 있음을 기억하십시오. 둘째, 앞으로 코드를 관리 할 사람이 있다는 것을 기억하십시오. 분명한 코드를 작성하는 것은 제공 할 수있는 가장 큰 도움 중 하나입니다 (깨진 코드가 얼마나 빠르지는 중요하지 않습니다). 셋째, 잠시 시간을 내서 http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/을 읽고 마음이 바뀔지 확인하십시오.
나는 잠을 잘 수 없다. 일부 함수가 반환 값 대신에 반환 매개 변수를 사용하여 최적화 될 수 있다는 것을 알았을 때 나는 쉽게 값을 반환해야 하는가 아니면 가능한 경우 반환 매개 변수를 사용해야 하는가? –
@SergiiBogomolov : 진정하십시오. 더 간단한 코드는 더 빠르고 더 빠릅니다. – GManNickG
이것은 답변이 아니지만 어떤 의미에서는 대답이기도합니다 ...
포인터로 인수를 취하는 함수가 제공되면 값으로 반환되고 컴파일러가 쉽게 최적화 할 수있는 함수를 생성하는 간단한 변환이 있습니다. T& obj
교체, 반환 형식을 추가 인수를 제거
void f(T *ptr) {
// uses ptr->...
}
기능에서 개체에 대한 참조를 추가하고 참조 이제
void f(T *ptr) { T & obj = *ptr; /* uses obj. instead of ptr-> */ }
와 PTR의 모든 사용을 대체
T obj
으로 변경하고 모든 수익률을 'obj'로 변경하십시오.T f() { T obj; // No longer a ref! /* code does not change */ return obj; }
이 시점에서 모든 return 문은 동일한 객체를 참조하기 때문에 NRVO가 중요하지 않은 값으로 반환하는 함수가 있습니다.
이 변환 기능은 포인터로 패스이 같은 단점들을 가지고 있지만, 그것은이 결코 나쁘다. 그러나 포인터를 통한 패스가 옵션 일 때마다 값으로 반환하는 것도 동일한 비용으로 옵션이라는 것을 보여줍니다.
정확하게 동일한 비용?
이것은 언어 이상이지만 컴파일러가 코드를 생성 할 때 컴파일러의 여러 실행 (또는 동일한 플랫폼의 다른 컴파일러)으로 코드 빌드가 상호 작용할 수있는 ABI (Application Binary Interface) . 현재 사용되는 모든 ABI는 return by value 함수에 공통된 특성을 공유합니다. (레지스터에 맞지 않음) 반환 유형의 경우 반환 된 객체의 메모리가 호출자에 의해 할당되고 함수는 그 기억. 당신이 대안을 비교한다면
void mangled_name_for_f(T* __result)
: 두 경우 모두 T t; f(&t);
및 T t = f();
를 생성 된 코드가 호출자의 프레임에 공간을 할당 컴파일러가
T f();
을 볼 때 즉, 호출 규칙이에 있다는 변환입니다 , [1], 포인터를 전달하는 함수를 호출합니다. 함수의 끝에서 컴파일러는 [2] 돌아올 것입니다. 여기서 [#]는 각 대안에서 객체의 생성자가 실제로 호출되는 위치입니다. 두 대안 모두의 비용은 동일합니다. 차이점은 [1] 객체가 기본값으로 생성되어야한다는 것이고 [2]에서는 객체의 최종 값을 이미 알고있을 수 있으며 더 효율적인 작업을 수행 할 수 있다는 것입니다.
실적과 관련하여 모든 것이 있습니까?
아니요. 나중에 값으로 인수를 취하는 함수에 해당 객체를 전달해야 할 경우에는 포인터로 전달할 경우 호출자의 스택에 명명 된 객체가 있으므로 객체를 복사 (또는 이동)해야합니다 (예 : void g(T value)
) 호출 규칙에 값 인수가 필요한 위치로 이동합니다.값에 의한 반환의 경우 컴파일러는 을 호출한다는 것을 알고 f()
에서 반환 된 객체의 유일한 사용은 g()
의 인수이므로을 호출 할 때 적절한 위치에 대한 포인터를 전달할 수 있습니다. 완료된 사본이 없을 것임을 의미합니다. 이 시점에서 설명서 접근 방식은 f
의 구현이 위의 바보 변환을 사용하더라도 컴파일러의 접근 방식 인 에서 실제로 시작됩니다!
T obj; // default initialize
f(&obj); // assign (or modify in place)
g(obj); // copy
g(f()); // single object is returned and passed to g(), no copies
이것은 좋은 설명입니다. 감사합니다. –
때때로 값을 반환하거나 함수 인수를 복사해야하기 때문에. – juanchopanza