TL; DR : const 참조를 통한 전달은 C ++에서 여전히 좋은 생각입니다. 조기 최적화가 아닙니다.
TL; DR2 : 대부분의 격언은 그럴 때까지 말이되지 않습니다.
목표
이 답변은 C ++ 핵심 지침 (amon의 의견에서 처음 언급 됨)에서 연결된 항목을 약간 확장하려고합니다 .
이 답변은 프로그래머의 영역 내에서 널리 퍼진 다양한 격언, 특히 상충되는 결론 또는 증거 사이의 화해 문제를 올바르게 생각하고 적용하는 방법에 대한 문제를 다루려고하지 않습니다.
적용 성
이 답변은 함수 호출 (같은 스레드에서 분리 할 수없는 중첩 범위)에만 적용됩니다.
(측면 참고) 통과 가능한 항목이 범위를 벗어날 수있는 경우 (즉, 수명이 외부 범위를 초과 할 수있는 경우) 다른 것보다 먼저 응용 프로그램의 객체 수명 관리 요구를 충족시키는 것이 중요합니다. 일반적으로 스마트 포인터와 같이 수명 관리가 가능한 참조를 사용해야합니다. 대안은 관리자를 사용하는 것일 수 있습니다. 람다는 분리 가능한 범위의 일종입니다. 람다 캡처는 객체 범위를 갖는 것처럼 동작합니다. 따라서 람다 캡처에주의하십시오. 또한 람다 자체가 복사 또는 참조로 전달되는 방식에주의하십시오.
가치를 지날 때
변경 가능 통신 (공유 참조)이 필요하지 않은 스칼라 값 (머신 레지스터 내에 적합하고 의미 론적 값을 갖는 표준 프리미티브)의 경우 값을 전달하십시오.
수신자가 객체 또는 집계의 복제를 요구하는 경우, 수신자의 사본이 복제 된 객체의 필요성을 충족시키는 값으로 전달합니다.
참조로 전달할 때 등
다른 모든 상황에서는 포인터, 참조, 스마트 포인터, 핸들 (핸들-바디 관용구 등)을 사용하십시오.이 조언을 따를 때마다 평소대로 const-correctness의 원칙을 적용하십시오.
메모리 공간이 충분히 큰 사물 (집계, 객체, 배열, 데이터 구조)은 성능상의 이유로 항상 참조를 통한 전달을 용이하게하도록 설계되어야합니다. 이 조언은 수백 바이트 이상일 때 확실히 적용됩니다. 이 조언은 수십 바이트 일 때 경계선입니다.
특이한 패러다임
의도적으로 복사가 많은 특수 목적의 프로그래밍 패러다임이 있습니다. 예를 들어, 문자열 처리, 직렬화, 네트워크 통신, 격리, 써드 파티 라이브러리 랩핑, 공유 메모리 프로세스 간 통신 등. 이러한 응용 프로그램 영역 또는 프로그래밍 패러다임에서 데이터는 구조체에서 구조체로 복사되거나 때로는 다시 패키징됩니다. 바이트 배열.
최적화가 고려 되기 전에 언어 사양이이 답변에 미치는 영향
Sub-TL; DR 참조 전파는 코드를 호출하지 않아야합니다. const-reference로 전달하면이 기준을 충족합니다. 그러나 다른 모든 언어는이 기준을 손쉽게 충족시킵니다.
초보자 C ++ 프로그래머는이 섹션을 완전히 건너 뛰는 것이 좋습니다.
(이 섹션의 시작 부분은 gnasher729의 답변에서 부분적으로 영감을 얻었지만 다른 결론에 도달했습니다.)
C ++는 사용자 정의 복사 생성자와 할당 연산자를 허용합니다.
(이것은 놀랍고 유감스러운 대담한 선택이었습니다. 오늘날 언어 디자인에서 허용되는 표준과의 차이점입니다.)
C ++ 프로그래머가 정의하지 않더라도 C ++ 컴파일러는 언어 원칙에 따라 이러한 메소드를 생성 한 다음 이외의 코드를 추가로 실행해야하는지 여부를 결정해야합니다 memcpy
. 예를 들어,가 class
/ struct
이은을 포함std::vector
멤버 사본 생성자와 사소하지 않은 할당 연산자가 있어야합니다.
다른 언어에서는 언어 설계에 따라 객체에 참조 의미가 있기 때문에 복사 생성자와 객체 복제는 권장되지 않습니다 (응용 프로그램의 의미에 절대적으로 필요하거나 의미있는 경우 제외). 이러한 언어에는 일반적으로 범위 기반 소유권 또는 참조 계산 대신 도달 가능성을 기반으로하는 가비지 수집 메커니즘이 있습니다.
C ++ (또는 C)에서 참조 또는 포인터 (const 참조 포함)가 전달되면 프로그래머는 주소 값의 전파 외에는 특별한 코드 (사용자 정의 또는 컴파일러 생성 함수)가 실행되지 않습니다. (참조 또는 포인터). 이것은 C ++ 프로그래머들이 익숙한 행동의 명확성입니다.
그러나 배경은 C ++ 언어가 불필요하게 복잡하여 이러한 행동의 명확성이 핵 낙진 지역 어딘가에있는 오아시스 (생존 가능한 서식지)와 같다는 것입니다.
더 많은 축복 (또는 모욕)을 추가하기 위해 C ++은 사용자 정의 이동 연산자 (이동 생성자 및 이동 할당 연산자)를 쉽게 수행 할 수 있도록 범용 참조 (r- 값)를 도입합니다. 이는 복사 및 딥 클로닝의 필요성을 줄임으로써 관련성이 높은 사용 사례 (한 인스턴스에서 다른 인스턴스로 오브젝트를 이동 (전송))하는 데 도움이됩니다. 그러나 다른 언어에서는 그러한 물체의 이동에 대해 말하는 것이 비논리적입니다.
(주제 이외의 섹션) "Want Speed? Pass by Value!"기사 전용 섹션입니다. 2009 년경.
이 기사는 2009 년에 작성되었으며 C ++의 r- 값에 대한 설계 정당성을 설명합니다. 이 기사는 이전 섹션의 결론에 대한 유효한 반론을 제시합니다. 그러나 기사의 코드 예제와 성능 주장은 오랫동안 반박되었습니다.
Sub-TL; DR C ++에서 r- 값 시맨틱의 설계는 놀랍도록 우아한 사용자 측 시맨틱을 허용합니다.Sort
예를 들어 함수에서 . 이 우아함은 다른 언어로 모델링 (모방) 할 수 없습니다.
정렬 함수는 전체 데이터 구조에 적용됩니다. 위에서 언급했듯이 많은 복사가 포함되면 속도가 느려집니다. 성능 최적화 (실제로 관련성이 있음)로서 정렬 함수는 C ++ 이외의 다른 언어에서는 파괴적으로 설계되었습니다. 파괴는 목표 데이터 구조가 정렬 목표를 달성하도록 수정되었음을 의미합니다.
C ++에서 사용자는 두 가지 구현 중 하나를 선택할 수 있습니다. 더 나은 성능을 가진 파괴적인 것 또는 입력을 수정하지 않는 일반적인 것입니다. 간결성을 위해 템플릿은 생략되었습니다.
/*caller specifically passes in input argument destructively*/
std::vector<T> my_sort(std::vector<T>&& input)
{
std::vector<T> result(std::move(input)); /* destructive move */
std::sort(result.begin(), result.end()); /* in-place sorting */
return result; /* return-value optimization (RVO) */
}
/*caller specifically passes in read-only argument*/
std::vector<T> my_sort(const std::vector<T>& input)
{
/* reuse destructive implementation by letting it work on a clone. */
/* Several things involved; e.g. expiring temporaries as r-value */
/* return-value optimization, etc. */
return my_sort(std::vector<T>(input));
}
/*caller can select which to call, by selecting r-value*/
std::vector<T> v1 = {...};
std::vector<T> v2 = my_sort(v1); /*non-destructive*/
std::vector<T> v3 = my_sort(std::move(v1)); /*v1 is gutted*/
정렬 이외에도이 우아함은 재귀 적 분할에 의해 배열 (처음 정렬되지 않은)에서 파괴적인 중앙값 찾기 알고리즘을 구현하는 데 유용합니다.
그러나 대부분의 언어는 배열에 파괴적인 정렬 알고리즘을 적용하는 대신 균형 잡힌 이진 검색 트리 접근 방식을 정렬에 적용합니다. 따라서이 기술의 실제 관련성은 그다지 높지 않습니다.
컴파일러 최적화가이 답변에 미치는 영향
인라인 (및 전체 프로그램 최적화 / 링크 타임 최적화)이 여러 수준의 함수 호출에 적용되면 컴파일러는 데이터 흐름을 볼 수 있습니다 (때로는 철저하게). 이 경우 컴파일러는 많은 최적화를 적용 할 수 있으며 그 중 일부는 메모리에 전체 객체를 생성하지 않아도됩니다. 일반적으로이 상황이 적용되는 경우 컴파일러가 철저하게 분석 할 수 있으므로 매개 변수가 값 또는 const-reference로 전달되는지는 중요하지 않습니다.
그러나 하위 수준 함수가 분석 이외의 것을 호출하는 경우 (예 : 컴파일 외부의 다른 라이브러리 또는 너무 복잡한 호출 그래프) 컴파일러는 방어 적으로 최적화해야합니다.
머신 레지스터 값보다 큰 객체는 명시 적 메모리로드 / 저장 명령어 또는 적절한 memcpy
기능 호출에 의해 복사 될 수 있습니다 . 일부 플랫폼에서 컴파일러는 두 개의 메모리 위치 사이를 이동하기 위해 SIMD 명령어를 생성하며 각 명령어는 수십 바이트 (16 또는 32) 씩 이동합니다.
상세 또는 시각적 혼란 문제에 대한 토론
C ++ 프로그래머는 이것에 익숙합니다. 즉, 프로그래머가 C ++를 싫어하지 않는 한 소스 코드에서 const-reference를 작성하거나 읽는 오버 헤드는 끔찍하지 않습니다.
비용-편익 분석은 여러 번 수행되었을 수 있습니다. 인용해야 할 과학적인 것들이 있는지 모르겠습니다. 대부분의 분석은 과학적이지 않거나 재현 할 수없는 것 같습니다.
여기에 내가 상상 한 것이 있습니다 (증거 또는 신뢰할만한 참조가없는) ...
- 예,이 언어로 작성된 소프트웨어의 성능에 영향을줍니다.
- 컴파일러가 코드의 목적을 이해할 수 있다면 잠재적으로 자동화 할 수있을 정도로 똑똑 할 수 있습니다
- 불행히도, 가변성을 선호하는 언어 (기능적 순도와 반대되는 언어)에서 컴파일러는 대부분의 것들이 변형 된 것으로 분류하므로, constness의 자동 추론은 대부분의 것들을 불변으로 거부합니다
- 정신적 오버 헤드는 사람들에 달려 있습니다. 이것을 높은 정신적 오버 헤드로 생각하는 사람들은 C ++을 실행 가능한 프로그래밍 언어로 거부했을 것입니다.