C++에서는 값으로 전달하는 것이 더 낫습니까, 아니면 상수 참조로 전달하는 것이 더 좋습니까?

StackOverflow https://stackoverflow.com/questions/270408

문제

C++에서는 값으로 전달하는 것이 더 낫습니까, 아니면 상수 참조로 전달하는 것이 더 좋습니까?

어느 것이 더 좋은 습관인지 궁금합니다.변수의 복사본을 만들지 않기 때문에 상수 참조를 통한 전달이 프로그램 성능을 향상시켜야 한다는 것을 알고 있습니다.

도움이 되었습니까?

해결책

일반적으로 권장되는 모범 사례였습니다.1 에게 const ref로 전달을 사용하십시오. 모든 유형, 내장 유형 제외(char, int, double, 등), 반복자 및 함수 객체의 경우 (람다, 다음에서 파생된 클래스 std::*_function).

특히나 존재하기 전에는 그랬다. 이동 의미론.이유는 간단합니다.값으로 전달하는 경우 개체의 복사본을 만들어야 하며 매우 작은 개체를 제외하고 이는 참조를 전달하는 것보다 항상 더 비쌉니다.

C++11을 통해 우리는 다음과 같은 이점을 얻었습니다. 이동 의미론.간단히 말해서, 이동 의미론을 통해 어떤 경우에는 객체를 복사하지 않고 "값으로" 전달할 수 있습니다.특히, 전달하는 객체가 다음과 같은 경우입니다. rvalue.

그 자체로 개체를 이동하는 것은 여전히 ​​참조로 전달하는 것만큼 비용이 많이 듭니다.그러나 대부분의 경우 함수는 어쨌든 객체를 내부적으로 복사합니다.그것은 걸릴 것이다 소유권 논쟁의.2

이러한 상황에서는 다음과 같은 (단순화된) 절충안이 있습니다.

  1. 객체를 참조로 전달한 다음 내부적으로 복사할 수 있습니다.
  2. 값으로 객체를 전달할 수 있습니다.

"값으로 전달"을 사용하면 개체가 rvalue가 아닌 한 개체가 복사됩니다.rvalue의 경우 대신 개체를 이동할 수 있으므로 두 번째 경우는 갑자기 더 이상 "복사한 다음 이동"이 아니라 "이동한 다음 (잠재적으로) 다시 이동"이 됩니다.

적절한 이동 생성자(예: 벡터, 문자열 등)를 구현하는 대형 객체의 경우 두 번째 경우는 다음과 같습니다. 크게 첫 번째보다 더 효율적입니다.따라서 다음을 수행하는 것이 좋습니다. 함수가 인수의 소유권을 갖고 객체 유형이 효율적인 이동을 지원하는 경우 값으로 전달을 사용하십시오..


역사적 참고 사항:

실제로 최신 컴파일러는 값을 전달하는 데 비용이 많이 드는 시점을 파악하고 가능하면 const ref를 사용하도록 호출을 암시적으로 변환할 수 있어야 합니다.

이론에 의하면. 실제로 컴파일러는 함수의 바이너리 인터페이스를 손상시키지 않고 항상 이를 변경할 수는 없습니다.일부 특수한 경우(함수가 인라인된 경우) 컴파일러가 원래 객체가 함수의 작업을 통해 변경되지 않는다는 것을 알아낼 수 있으면 복사본이 실제로 제거됩니다.

그러나 일반적으로 컴파일러는 이를 결정할 수 없으며 C++의 이동 의미론으로 인해 이 최적화의 관련성이 훨씬 낮아졌습니다.


1 예:스콧 마이어스에서는 효과적인 C++.

2 이는 특히 인수를 가져와 내부적으로 생성된 객체 상태의 일부로 저장할 수 있는 객체 생성자의 경우에 해당됩니다.

다른 팁

편집하다: CPP-Next에 대한 Dave Abrahams의 새 기사 :

속도를 원하십니까? 가치로 통과하십시오.


복사가 저렴한 structs의 값에 따라 통과하면 컴파일러가 객체가 별명이 아니라고 가정 할 수있는 추가 이점이 있습니다 (동일한 개체가 아님). 통과 회의를 사용하여 컴파일러는 항상이를 가정 할 수 없습니다. Simple example:

foo * f;

void bar(foo g) {
    g.i = 10;
    f->i = 2;
    g.i += 5;
}

컴파일러는이를 최적화 할 수 있습니다

g.i = 15;
f->i = 2;

F와 G가 동일한 위치를 공유하지 않는다는 것을 알고 있기 때문입니다. G가 참조 (foo &) 인 경우 컴파일러는이를 가정 할 수 없었습니다. GI는 F-> I에 의해 별명을받을 수 있고 7의 값을 가져야하므로 컴파일러는 메모리에서 새로운 GI 값을 다시 가져와야합니다.

보다 prical 규칙에 대해서는 다음과 같은 좋은 규칙이 있습니다. 생성자를 이동하십시오 기사 (적극 권장되는 독서).

  • 함수가 부작용으로 인수를 변경하려는 경우, 비 참조로 참조하십시오.
  • 함수가 인수를 수정하지 않고 인수가 원시 유형이라면 값으로 가져 가십시오.
  • 그렇지 않으면 다음 경우를 제외하고 Const 참조로 가져 가십시오.
    • 그렇다면 함수가 어쨌든 const 참조의 사본을 만들어야한다면 값으로 가져 가십시오.

위의 "원시"는 기본적으로 몇 바이트 길이이며 다형성 (반복자, 기능 개체 등)이 아니거나 복사 비용이 많이 드는 작은 데이터 유형을 의미합니다. 그 논문에는 또 다른 규칙이 있습니다. 아이디어는 때때로 사본을 만들기를 원한다는 것입니다 (인수를 수정할 수없는 경우), 때로는 원하지 않는 경우가 있습니다 (어쨌든 인수가 일시적인 경우 기능에 인수 자체를 사용하고 싶을 경우에는 , 예를 들어). 이 논문은 그것이 어떻게 할 수 있는지 자세히 설명합니다. C ++ 1X에서는이 기술이 언어 지원과 함께 기본적으로 사용될 수 있습니다. 그때까지 나는 위의 규칙을 가지고 갈 것입니다.

예제 : 문자열 대문자를 만들고 대문자 버전을 반환하려면 항상 가치별로 통과해야합니다. 어쨌든 사본을 가져와야합니다 (구성 참조를 직접 변경할 수는 없습니다) - 가능한 한 투명하게 투명하게 만들 수 있습니다. 발신자와 해당 사본을 일찍 만들어 발신자가 가능한 한 최적화 할 수 있도록 해당 논문에 자세히 설명 할 수 있습니다.

my::string uppercase(my::string s) { /* change s and return it */ }

그러나 어쨌든 매개 변수를 변경할 필요가 없으면 const를 참조하여 가져옵니다.

bool all_uppercase(my::string const& s) { 
    /* check to see whether any character is uppercase */
}

그러나 매개 변수의 목적이 인수에 무언가를 쓰는 것이라면, 비 참조로 전달하십시오.

bool try_parse(T text, my::string &out) {
    /* try to parse, write result into out */
}

유형에 따라 다릅니다. 당신은 참조와 부정을 만들어야한다는 작은 오버 헤드를 추가하고 있습니다. 기본 사본 CTOR를 사용하는 포인터와 크기가 동일하거나 작은 유형의 경우 값을 통과하는 것이 더 빠를 수 있습니다.

지적한 바와 같이, 그것은 유형에 따라 다릅니다. 내장 데이터 유형의 경우 가치별로 전달하는 것이 가장 좋습니다. 한 쌍의 ints와 같은 매우 작은 구조조차도 가치로 통과함으로써 더 나은 성능을 발휘할 수 있습니다.

다음은 정수 값이 있고 다른 루틴으로 전달한다고 가정합니다. 해당 값이 레지스터에 저장되도록 최적화 된 경우, 전달하려면 참조가 되려면 먼저 메모리에 저장된 다음 스택에있는 메모리에 대한 포인터를 사용하여 통화를 수행해야합니다. 가치로 통과 된 경우 필요한 것은 스택에 푸시 된 레지스터 만 있으면됩니다. (세부 사항은 다른 호출 시스템과 CPU가 주어진 것보다 조금 더 복잡합니다).

템플릿 프로그래밍을 수행하는 경우 일반적으로 전달되는 유형을 모르기 때문에 항상 Const Ref를 통과해야합니다. 가치로 나쁜 것을 통과하기위한 처벌은 내장 유형을 통과하는 처벌보다 훨씬 나쁩니다. Const Ref.

답을 얻은 것 같네요. 가치로 통과하는 것은 비싸지 만 필요한 경우 작업 할 사본을 제공합니다.

이것이 제가 비 템플릿 함수의 인터페이스를 설계 할 때 일반적으로 작업하는 것입니다.

  1. 함수가 매개 변수를 수정하지 않으려면 값으로 통과하고 값이 복사하기에 저렴합니다 (int, double, float, char, bool 등 표준 라이브러리의 컨테이너는 그렇지 않습니다)

  2. 값이 복사하는 데 비싸고 함수가 지적 된 값을 수정하고 싶지 않은 경우 Const 포인터를 통과합니다. NULL은 함수가 처리하는 값입니다.

  3. 값이 복사하는 데 비싸고 함수가 지적 된 값을 수정하려면 값을 수정하고 NULL이 함수가 처리하는 값이면 전달되지 않은 포인터를 통과하십시오.

  4. Const 참조 값이 복사 비용이 비싸고 함수가 참조 된 값을 수정하려고하지 않으며 포인터가 대신 사용 된 경우 NULL은 유효한 값이 아닙니다.

  5. 값이 복사하는 데 비싸고 함수가 참조 된 값을 수정하려면 값이 비싸지 않으며 포인터를 대신 사용하는 경우 NULL이 유효한 값이되지 않을 때 전달되지 않습니다.

규칙적으로 Const 참조를 통과하는 것이 더 좋습니다. 그러나 로컬에서 기능 인수를 수정 해야하는 경우 가치별로 통과하는 것이 좋습니다. 일부 기본 유형의 경우 일반적으로 성능은 가치별로 통과하고 참조하여 동일합니다. 실제로 포인터로 내부적으로 표현되는 참조는 예를 들어 포인터의 경우 두 통과가 성능 측면에서 동일하거나 불필요한 부정으로 인해 가치를 통과하는 것이 더 빠를 수 있다고 기대할 수 있습니다.

경험상 비 클래스 유형의 가치 및 클래스에 대한 Const Reference의 가치. 클래스가 실제로 작 으면 가치를 따라 통과하는 것이 낫지 만 차이는 최소화됩니다. 당신이 정말로 피하고 싶은 것은 가치로 거대한 클래스를 통과하고 그것을 모두 복제하는 것입니다. 이것은 당신이 지나가는 요소가있는 std :: 벡터를 지나가는 경우 큰 차이를 만들 것입니다.

작은 유형에 대한 가치별로 통과하십시오.

큰 유형에 대한 Const 참조 (큰 정의의 정의는 기계마다 다를 수 있음) 그러나 C ++ 11에서는 움직임 시맨틱을 이용할 수 있으므로 데이터를 소비하려는 경우 값을 통과합니다. 예를 들어:

class Person {
 public:
  Person(std::string name) : name_(std::move(name)) {}
 private:
  std::string name_;
};

이제 호출 코드는 다음과 같습니다.

Person p(std::string("Albert"));

그리고 하나의 객체 만 생성되어 멤버로 직접 이동합니다. name_ 클래스 Person. const 참조로 통과하면 그것을 넣기 위해 사본을 만들어야합니다. name_.

간단한 차이 :- 함수에는 입력 및 출력 매개 변수가 있으므로 전달 입력 및 출력 매개 변수가 동일하면 입력 및 출력 매개 변수가 다른 경우 값으로 호출을 사용하는 것이 더 좋습니다.

예시 void amount(int account , int deposit , int total )

입력 매개 변수 : 계정, 입금 출력 매개 변수 : 총

입력 및 출력은 Vaule의 다른 사용 통화입니다

  1. void amount(int total , int deposit )

입력 총 예금 출력 총계

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top