매개변수 vs 인자
함수의 **매개변수(parameter)**는 정의에 적힌 이름이 있는 변수이고, **인자(argument)**는 함수를 호출할 때 실제로 건네는 값입니다. 이전 페이지에서는 함수를 정의하고 호출하는 방법을 보여주었고, 이 페이지에서는 그 값들이 실제로 어떻게 안으로 들어가는지를 다룹니다. C++은 여러 가지 방법을 제공하며, 그 선택이 정확성과 속도 모두에 영향을 주기 때문입니다.
C++의 기본은 값 전달입니다. 함수는 복사본을 받습니다.
addTen 안에서 n은 score로부터 초기화된 별개의 변수입니다. n을 재할당해도 그 복사본만 건드리므로, main으로 돌아오면 score는 그대로입니다. 이것은 안전하고 예측 가능하며 - 함수가 실수로 데이터를 망가뜨릴 수 없습니다 - 바로 그래서 기본 방식인 것입니다.
참조 전달: 함수가 호출자를 변경하게 하기
때로는 함수가 호출자의 변수를 수정하기를 원할 때가 있습니다. 매개변수 타입에 &를 붙이면 그것은 참조, 즉 복사본이 아닌 원본의 별칭이 됩니다.
첫 예제와의 유일한 차이는 &이지만, 이제 n과 score는 같은 객체입니다. 이것은 둘 이상의 값을 "반환"하거나 무언가를 제자리에서 갱신하는 표준적인 방법입니다. 고전적인 활용은 두 변수를 교환하는 것입니다.
&가 없으면 swapValues는 두 복사본을 뒤섞을 뿐이고 main은 아무런 변화도 보지 못합니다 - 매우 흔한 초보자의 버그입니다.
const 참조: 저렴한 읽기 전용 접근
값 전달은 인자를 복사합니다. int라면 비용이 들지 않지만, 큰 string이나 vector를 호출할 때마다 복사하는 것은 실제로 낭비되는 작업입니다. 해결책은 const 참조(const T&)입니다. 복사 없이 참조의 속도를 얻으면서, 동시에 인자를 수정하지 않겠다는 컴파일러가 강제하는 약속을 얻습니다.
유용한 경험 법칙: 작은 내장 타입(int, double, char, bool, 포인터)은 값으로 전달하고, 읽기만 하면 되는 큰 객체는 const 참조로 전달하세요. 일반(const가 아닌) T&는 호출자의 객체를 정말로 수정할 의도가 있는 경우를 위해 남겨 두세요.
미묘한 함정: 일반 int& n은 임시 객체나 리터럴에 바인딩될 수 없습니다. 첫 예제의 addTen(5)는 매개변수가 int&였다면 컴파일되지 않습니다. 5는 별칭을 붙일 수 있는 변수가 아니기 때문입니다. 반면 const int&는 5에 바인딩될 수 있는데, 이것이 const 참조가 그토록 널리 쓰이는 또 하나의 이유입니다.
기본 인자
매개변수에 대체 값을 줘서 호출자가 그것을 생략할 수 있게 할 수 있습니다. 인자가 없으면 기본값이 사용됩니다.
사람들이 걸려 넘어지는 규칙이 두 가지 있습니다. 첫째, 기본값은 뒤쪽에 와야 합니다 - 어떤 매개변수가 기본값을 가지면 그 뒤의 모든 매개변수도 기본값을 가져야 합니다. void f(int a = 1, int b)라고 쓸 수 없는데, a를 건너뛰고 b만 넘길 방법이 없기 때문입니다. 둘째, 함수를 헤더에 선언하고 다른 곳에서 정의할 때는 기본값을 선언에만 넣고, 정의에서는 절대 반복하지 마세요 - 반복하면 컴파일 오류입니다.
배열과 벡터 전달하기
원시 배열은 전달될 때 포인터로 붕괴(decay)하므로 함수는 그 크기를 잃어버립니다 - 거의 항상 길이를 함께 전달합니다.
배열이 포인터가 되었기 때문에, sum 안에서의 sizeof(arr)는 배열이 아니라 포인터의 크기를 반환합니다 - 악명 높은 버그입니다. 현대 C++에서는 자신의 크기를 지니고 다니는 std::vector(또는 C++20의 std::span)를 const 참조로 전달하는 것을 선호하세요.
const&에 주목하세요. 이를 빼면 호출할 때마다 벡터 전체가 복사됩니다. 원소 네 개짜리 벡터라면 무해하지만, 100만 개라면 조용한 성능 누수가 됩니다.
포인터 매개변수
포인터(T*)를 전달할 수도 있습니다. 참조처럼 이것도 함수가 호출자의 데이터에 닿을 수 있게 하지만, 포인터는 다시 가리키게 하거나 널(null)이 될 수 있습니다 - 그래서 "값 없음"이 정당한 선택지일 때 적합한 도구입니다.
호출자는 주소를 공유하기 위해 &value를 전달하고, 함수는 *out을 통해 씁니다. 참조와의 핵심 차이는 포인터가 nullptr일 수 있다는 점입니다. 따라서 포인터를 받는 함수는 역참조하기 전에 검사해야 합니다 - 그 방어를 건너뛰고 널 포인터를 역참조하는 것은 미정의 동작이며, 보통은 크래시입니다. "값 없음"이 결코 의미가 없다면, 애초에 널이 될 수 없는 참조가 더 깔끔합니다.
다음: 참조
매개변수는 참조가 제 역할을 톡톡히 하는 곳이지만, 참조는 그 자체로 하나의 기능입니다 - 함수 시그니처 안에서만이 아니라 어떤 변수에든 만들 수 있는 별칭이지요. 다음 페이지에서는 참조가 단독으로 어떻게 동작하는지를 깊이 파고듭니다. 선언하는 방법, 왜 즉시 초기화해야 하는지, lvalue 참조와 const 참조의 차이, 그리고 참조가 어떻게 매달린(dangling) 상태가 될 수 있는지의 미묘한 지점들을 살펴봅니다.
자주 묻는 질문
C++에서 값 전달과 참조 전달의 차이는 무엇인가요?
값 전달은 인자를 매개변수로 복사하므로 함수 내부의 변경이 호출자에게 영향을 주지 않습니다. 참조 전달(int&)은 매개변수를 호출자 변수의 별칭으로 만들기 때문에 변경 사항이 외부에서도 보입니다. 복사하려면 void f(int x)를, 원본을 수정하려면 void f(int& x)를 사용하세요.
C++에서 const 참조 매개변수는 언제 사용해야 하나요?
큰 객체를 복사하지 않고, 함수가 수정하지도 못하게 하면서 읽고 싶을 때 const T&를 사용하세요 - 예를 들어 void print(const string& s)입니다. 참조 전달의 속도와 값 전달의 안전성을 함께 얻을 수 있습니다. int나 char 같은 작은 타입의 경우 일반 값 전달도 똑같이 빠릅니다.
C++의 기본 인자란 무엇인가요?
기본 인자는 호출자가 매개변수를 생략했을 때 매개변수가 대체 값을 갖게 해줍니다. 예: void greet(string name = "there"). 기본값은 뒤쪽(가장 오른쪽) 매개변수여야 하며, 선언과 정의가 분리되어 있다면 선언에만 지정하고 정의에는 지정하지 않습니다.