C ++에서 값으로 전달하거나 상수 참조로 전달하는 것이 더 낫습니까?


답변:


203

그것은 일반적으로 가장 좋은 방법은 추천하는 데 사용 1대한 const를 심판하여 사용 패스 모든 종류의 유형 (내장을 제외하고 char, int, double반복자를 들어, 등), 함수 객체에 대한 (에서 파생 람다, 클래스 std::*_function).

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

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

그 자체로 객체를 이동하는 것은 여전히 ​​참조로 전달하는 것만 큼 비싸다. 그러나 많은 경우 함수는 내부적으로 객체를 복사합니다. 즉 , 인수의 소유권 을 갖습니다. 2

이러한 상황에서 우리는 다음과 같은 (단순화 된) 장단점이 있습니다.

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

객체가 rvalue가 아닌 한 "값별 패스"는 여전히 객체를 복사합니다. rvalue의 경우 객체를 대신 이동할 수 있으므로 두 번째 경우가 더 이상 "복사, 이동"하지 않고 "이동 한 다음 (잠재적으로) 다시 이동"됩니다.

적절한 이동 생성자 (벡터, 문자열 등)를 구현하는 큰 객체의 경우 두 번째 경우는 첫 번째보다 훨씬 더 효율적입니다. 따라서 함수가 인수의 소유권을 가져 오거나 객체 유형이 효율적인 이동을 지원하는 경우 값으로 전달사용 하는 것이 좋습니다 .


역사적 메모 :

실제로 모든 최신 컴파일러는 값으로 전달하는 것이 비쌀 때 알아낼 수 있어야하며 가능하면 const 참조를 사용하도록 호출을 암시 적으로 변환해야합니다.

이론에 의하면. 실제로 컴파일러는 함수의 이진 인터페이스를 중단하지 않고 항상 이것을 변경할 수는 없습니다. 일부 특수한 경우 (함수가 인라인 될 때) 컴파일러가 함수의 조치를 통해 원래 오브젝트가 변경되지 않음을 알 수있는 경우 사본이 실제로 제거됩니다.

그러나 일반적으로 컴파일러는 이것을 결정할 수 없으며 C ++에서 이동 의미의 출현으로 인해이 최적화가 훨씬 덜 적합 해졌습니다.


1 예 : Scott Meyers의 효과적인 C ++ .

2 이것은 객체 생성자의 경우에 특히 그렇습니다. 인수는 인수를 받아서 내부에 생성 된 객체 상태의 일부로 저장할 수 있습니다.


흠 ... 심판이 지나갈 가치가 있는지 확실하지 않습니다. double-s
sergtk

3
평소와 같이 boost는 여기서 도움이됩니다. boost.org/doc/libs/1_37_0/libs/utility/call_traits.htm 에는 유형이 내장 유형 (때로는 쉽게 알 수없는 경우에 유용) 인 경우 유형을 자동으로 파악할 수있는 템플릿 항목이 있습니다.
CesarB

13
이 답변은 중요한 요점을 놓치고 있습니다. 슬라이싱을 피하려면 참조 (const 또는 달리)로 전달해야합니다. 참조 stackoverflow.com/questions/274626/...
ChrisN

6
@Chris : 그렇습니다. 나는 완전히 다른 의미론이기 때문에 다형성의 전체 부분을 생략했습니다. 나는 OP가 (의미 적으로) "가치에 의한"논쟁의 통과를 의미한다고 생각한다. 다른 의미론이 필요할 때는 질문 자체가 제기되지도 않습니다.
Konrad Rudolph

98

편집 : cpp-next의 Dave Abrahams의 새 기사 :

속도를 원하십니까? 값으로 전달하십시오.


복사가 저렴한 구조체의 값으로 전달하면 컴파일러가 객체가 별칭이 아닌 것으로 간주 할 수 있다는 추가 이점이 있습니다 (동일한 객체가 아님). 패스 바이 레퍼런스를 사용하면 컴파일러는 항상이를 가정 할 수 없습니다. 간단한 예 :

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 값을 다시 가져와야합니다.

보다 실용적인 규칙을 보려면 Move Constructors 기사 (권장 사항) 에서 찾아 볼 수있는 규칙이 있습니다.

  • 함수가 인수를 부작용으로 변경하려는 경우, 상수가 아닌 참조로 가져 가십시오.
  • 함수가 인수를 수정하지 않고 인수가 기본 유형 인 경우 값별로 가져옵니다.
  • 그렇지 않으면 다음 경우를 제외하고 const 참조로 가져옵니다.
    • 함수가 const 참조의 사본을 만들어야 할 경우 값으로 가져옵니다.

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

예 : 문자열을 대문자로 만들고 대문자 버전을 반환하려면 항상 값을 전달해야합니다. 어쨌든 사본을 가져와야합니다 (const 참조를 직접 변경할 수는 없습니다)-가능한 한 투명하게 만드는 것이 좋습니다. 발신자가이 문서에 자세히 설명 된대로 최대한 많이 최적화 할 수 있도록 사본을 조기에 만드십시오.

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 */
}

나는 당신의 규칙이 좋았지 만 심판이 그것을 통과시키지 않는 것에 대해 이야기하는 첫 번째 부분에 대해서는 확신하지 못합니다. 네, 물론, 참고로 뭔가를 전달하지 않는 것은 단지 최적화의 cuss는 전혀 의미가 없습니다. 전달하는 스택 객체를 변경하려면 ref로 변경하십시오. 당신이하지 않으면 값으로 전달하십시오. 변경하고 싶지 않으면 const-ref로 전달하십시오. 값으로 전달되는 최적화는 참조로 전달할 때 다른 항목을 얻으므로 중요하지 않습니다. 나는 "원하는 속도"를 이해하지 못한다? 당신이 그 작전을 수행 할 곳이라면 어쨌든 가치를 지날 것입니다 ..
chikuba

Johannes : 나는 그 기사를 읽을 때이 기사를 좋아 했지만, 그것을 시도했을 때 실망했습니다. 이 코드 는 GCC와 MSVC에서 모두 실패했습니다. 내가 뭔가를 놓쳤습니까, 아니면 실제로 작동하지 않습니까?
user541686

어쨌든 사본을 만들고 싶다면 const 참조 대신 값으로 전달 한 다음 이동한다는 데 동의하지 않습니다. 이 방법, 더 효율적인 것, 사본 및 이동 (앞으로 전달하면 사본이 2 개도 가능) 또는 사본입니까? 예. 어느 쪽이든 특별한 경우가 있지만 데이터를 어쨌든 이동할 수없는 경우 (예 : 정수 톤의 POD) 추가 사본이 필요하지 않습니다.
Ion Todirel

2
Mehrdad, 예상 한 바는 없지만 코드가 예상대로 작동
Ion Todirel

컴파일러가 유형이 언어의 결함과 겹치지 않는다는 것을 확신시키기 위해 복사가 필요하다고 생각합니다. __restrict__과도한 사본을 사용하는 것보다 GCC (참조 작업도 가능)를 사용하고 싶습니다 . 너무 나쁜 표준 C ++에서는 C99의 restrict키워드를 채택하지 않았습니다 .
Ruslan

12

유형에 따라 다릅니다. 참조 및 역 참조를 작성해야하는 작은 오버 헤드가 추가되었습니다. 기본 복사 ctor를 사용하는 포인터와 크기가 같거나 작은 유형의 경우 값을 전달하는 것이 더 빠를 수 있습니다.


네이티브가 아닌 유형의 경우 (컴파일러가 코드를 얼마나 잘 최적화하는지에 따라) 단지 참조 대신 const 참조를 사용하여 성능을 향상시킬 수 있습니다.
OJ.

9

지적했듯이 유형에 따라 다릅니다. 기본 제공 데이터 형식의 경우 값별로 전달하는 것이 가장 좋습니다. int 쌍과 같은 매우 작은 구조조차도 값을 전달함으로써 더 잘 수행 할 수 있습니다.

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

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


용어에 대한 참고 사항 : 백만 개의 정수를 포함하는 구조체는 여전히 "POD 유형"입니다. 아마도 '내장 유형의 경우 값으로 전달하는 것이 가장 좋습니다'를 의미합니다.
Steve Jessop

6

이것은 템플릿이 아닌 함수의 인터페이스를 디자인 할 때 일반적으로 작동하는 것입니다.

  1. 함수가 매개 변수를 수정하지 않으려는 경우 값을 복사하면 값이 복사하기에 저렴한 경우 (int, double, float, char, bool 등) std :: string, std :: vector 및 나머지는 표준 라이브러리의 컨테이너 중 하나가 아닙니다)

  2. 값을 복사하는 데 비용이 많이 들고 함수가 가리키는 값을 수정하지 않으려는 경우 const 포인터로 전달하고 NULL은 함수가 처리하는 값입니다.

  3. 값이 복사하는 데 비용이 많이 들고 함수가 가리키는 값을 수정하려고하고 NULL이 함수가 처리하는 값인 경우에는 상수가 아닌 포인터로 전달하십시오.

  4. 값이 복사하는 데 비용이 많이 들고 함수가 참조되는 값을 수정하지 않고 포인터가 대신 사용 된 경우 NULL은 유효한 값이 아닌 경우 const 참조로 전달하십시오.

  5. 값이 복사하기에 비싸고 함수가 참조 된 값을 수정하려고하고 포인터가 대신 사용 된 경우 NULL은 유효한 값이 아닌 경우 비정규 참조로 전달하십시오.


std::optional그림에 추가 하면 더 이상 포인터가 필요하지 않습니다.
바이올렛 기린

5

답을 얻은 것 같습니다. 값으로 전달하는 것은 비용이 많이 들지만 필요한 경우 사용할 사본을 제공합니다.


왜 이것이 투표에 실패했는지 잘 모르겠습니다. 이해가 되네요. 현재 저장된 값이 필요하면 값으로 전달하십시오. 그렇지 않은 경우 참조를 전달하십시오.
Totty

4
완전히 유형에 따라 다릅니다. POD (plain old data) 유형을 참조로 사용하면 실제로 더 많은 메모리 액세스가 발생하여 성능이 저하 될 수 있습니다.
Torlack

1
분명히 int를 참조로 전달해도 아무것도 저장되지 않습니다! 나는 그 질문이 포인터보다 큰 것들을 의미한다고 생각합니다.
GeekyMonkey

4
컴퓨터가 const ref로 간단한 것을 전달하는 방법을 정말로 이해하지 못하는 사람들이 많은 코드를 보았습니다.
Torlack

4

const 참조를 통과하는 규칙이 더 좋습니다. 그러나 함수 인수를 로컬로 수정 해야하는 경우 값을 전달하는 것이 좋습니다. 일부 기본 유형의 경우 성능은 일반적으로 값과 참조로 전달하는 데 동일합니다. 실제로 내부적으로 포인터로 표시되는 참조이므로 포인터의 경우 두 전달이 모두 성능면에서 동일하거나 값을 전달하는 것이 불필요한 역 참조로 인해 더 빠를 수 있다고 예상 할 수 있습니다.


수신자의 매개 변수 사본을 수정해야하는 경우 값을 전달하지 않고 호출 된 코드로 사본을 작성할 수 있습니다. IMO는 일반적으로 다음과 같은 구현 세부 사항에 따라 API를 선택해서는 안됩니다. 호출 코드의 소스는 어느 쪽이든 동일하지만 객체 코드는 다릅니다.
Steve Jessop

값으로 전달하면 복사가 작성됩니다. 그리고 IMO는 어떤 방법으로 사본을 작성하든 상관없이 값으로 전달되거나 로컬로 인수를 통해 C ++과 관련이 있습니다. 그러나 디자인 관점에서 나는 당신에 동의합니다. 그러나 여기서는 C ++ 기능 만 설명하고 디자인을 건드리지 않습니다.
sergtk

1

경험상 비 클래스 유형의 값과 클래스의 const 참조입니다. 클래스가 실제로 작은 경우 값으로 전달하는 것이 더 좋지만 차이는 최소화됩니다. 실제로 피하고 싶은 것은 값으로 거대한 클래스를 전달하고 모두 복제하는 것입니다. 예를 들어 std :: vector를 상당히 많은 요소로 전달하면 큰 차이가 있습니다.


내 이해는 std::vector실제로 해당 항목을 힙에 할당하고 벡터 객체 자체는 절대 커지지 않는다는 것입니다. 아 잠깐만 그러나 연산으로 인해 벡터 사본이 만들어지면 실제로 모든 요소가 복제됩니다. 나쁘다.
Steven Lu

1
네, 제가 생각한 것입니다. sizeof(std::vector<int>)상수이지만 값으로 전달하면 컴파일러 영리성이없는 경우에도 내용을 복사합니다.
Peter

1

작은 유형의 값으로 전달하십시오.

큰 유형에 대한 const 참조에 의한 전달 (big의 정의는 시스템마다 다를 수 있음) 그러나 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_.


-5

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

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

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

입력과 출력은 vaule에 의한 다른 사용 호출입니다

  1. void amount(int total , int deposit )

입력 총 입금 출력 총

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