C++에서 함수에 객체를 전달하는 방식은 크게 값 전달(Pass by Value), 참조 전달(Pass by Reference), 포인터 전달(Pass by Pointer) 세 가지로 나뉘며, 여기에 const를 조합한 변형들이 있습니다. 클래스 X의 객체를 함수 fun에 전달한다고 가정하고, 각 방식의 동작 원리와 차이점을 코드 예제와 함께 살펴보겠습니다.
1. 값으로 전달 (Pass by Value)
객체를 값으로 전달하면 함수 스코프 안에 해당 객체의 얕은 복사본(shallow copy)이 새로 생성됩니다. 따라서 함수 내부에서 객체를 아무리 수정해도 호출자가 넘긴 원본 객체에는 전혀 영향을 주지 않습니다.
선언
void fun(X x);
호출
X x;
fun(x);
다만 복사본이 만들어지는 만큼, 객체의 크기가 크거나 복사 생성자의 비용이 비싸다면 성능 저하가 발생할 수 있다는 점을 유의해야 합니다.
2. 참조로 전달 (Pass by Reference)
객체에 대한 참조(reference)를 전달하는 방식입니다. 함수 내에서 수행한 수정 사항이 그대로 원본 객체에 반영되며, 복사본이 생성되지 않아 성능 면에서도 유리합니다.
선언
void fun(X &x);
호출
X x;
fun(x);
3. 상수 참조로 전달 (Pass by const Reference)
객체에 대한 상수 참조(const reference)를 전달하는 방식입니다. 함수 내부에서 객체를 직접 수정하거나 재할당할 수 없으므로, 함수가 객체를 읽기 전용으로만 사용하도록 강제하고 싶을 때 매우 유용합니다. 복사본도 생성되지 않습니다.
선언
void fun(const X &x);
호출
X x;
fun(x);
복사 오버헤드 없이 원본 데이터를 보호할 수 있어, C++에서 객체를 함수 인자로 넘길 때 가장 널리 권장되는 방식 중 하나입니다.
4. 포인터로 전달 (Pass by Pointer)
객체의 주소(포인터)를 전달하는 방식으로, 참조 전달과 동작이 유사합니다. 복사본은 생성되지 않으며, 함수 내에서 포인터를 역참조하여 객체를 수정하면 원본에 그대로 반영됩니다.
선언
void fun(X *x);
호출
X x;
fun(&x);
참조와 달리 포인터는 nullptr을 전달할 수 있으므로, 함수 내부에서 유효성 검사가 필요할 수 있습니다.
5. 상수 포인터로 전달 (Pass by const Pointer)
상수 포인터를 전달하면 함수 내부에서 포인터 자체를 수정하거나 다른 주소로 재할당할 수 없습니다. 함수가 해당 객체의 주소만 고정적으로 사용하도록 제한할 때 유용하며, 역시 복사본은 생성되지 않습니다.
선언
void fun(X *const x);
호출
X x;
fun(&x);
만약 포인터가 가리키는 대상 객체까지 수정 불가능하게 하려면 const X *x 또는 const X *const x 형태로 선언하면 됩니다.
마치며: 어떤 방식을 선택해야 할까?
각 전달 방식은 목적에 따라 선택해야 합니다. 원본 보호가 필요하면 값 전달 또는 상수 참조, 원본 수정이 필요하면 참조나 포인터, 주소만 고정해서 사용하려면 상수 포인터가 적합합니다. 일반적인 경우에는 복사 비용이 없고 의도가 명확한 상수 참조 전달을 우선적으로 고려하는 것이 좋습니다.