const 참조와 값 의미론 (const Reference and Value Semantics)
개요
C++에서 함수에 인자를 전달하는 방식은 프로그램의 성능과 안정성에 큰 영향을 미칩니다. 특히 const
참조를 사용하는 것은 C++ 프로그래머가 반드시 이해해야 할 중요한 개념입니다. 이 문서는 값에 의한 전달(pass-by-value)과 참조에 의한 전달(pass-by-reference), 그리고 const
참조를 사용한 전달 방식의 차이점과 장점을 설명합니다.
- 값 의미론 (Value Semantics): 함수 호출 시 인자의 복사본을 만들어 전달합니다. 원본 데이터는 함수 내에서 변경되지 않으므로 안전하지만, 객체가 클 경우 복사 비용이 발생하여 성능 저하의 원인이 될 수 있습니다.
- 참조 의미론 (Reference Semantics): 객체의 복사본 대신 참조(별칭)를 전달합니다. 복사가 발생하지 않아 효율적이지만, 함수 내에서 원본 객체를 수정할 수 있습니다.
- Const 참조 (Const Reference): 참조의 효율성과 값의 안전성을 결합한 방식입니다. 객체를 복사하지 않으면서도 함수 내에서 원본이 수정되는 것을 컴파일러 수준에서 방지합니다.
C++ 버전별 주요 키워드 도입 시기
- C++98:
const
키워드와 참조(&
)는 C++ 초기 버전부터 존재했습니다. 이들을 조합한const
참조는 전통적으로 C++에서 효율성과 안전성을 모두 잡기 위한 핵심적인 기능으로 사용되었습니다. - C++11: 예제 코드에 사용된
std::array
와std::uint32_t
같은 고정 크기 정수 타입이 표준에 도입되었습니다.std::array
는 C 스타일 배열보다 안전하고 편리한 기능을 제공합니다.
내용 설명
함수에 인자를 전달하는 세 가지 주요 방식을 예제 코드와 함께 살펴보겠습니다.
const std::array<std::uint32_t, N> &arr
(const 참조에 의한 전달)print_array_values
함수는 배열을 화면에 출력하기만 할 뿐, 내용을 수정하지 않습니다.- 이 경우,
const
참조를 사용하여 불필요한 데이터 복사를 피하고(성능 향상), 동시에 함수가 원본 배열을 실수로 변경하는 것을 막습니다(안전성 확보). &
기호는 이 매개변수가 참조임을 나타내며,const
는 참조하는 대상을 변경할 수 없음을 의미합니다.
std::array<std::uint32_t, N> &arr
(참조에 의한 전달)double_value
함수는 배열의 각 요소 값을 2배로 변경해야 합니다. 즉, 원본 데이터를 수정해야 합니다.- 이럴 때는
const
를 제외한 참조(&
)를 사용하여 원본 배열에 직접 접근하고 수정할 수 있도록 합니다. - 이 방식 역시 데이터 복사가 발생하지 않아 효율적입니다.
std::array<std::uint32_t, N> arr
(값에 의한 전달 - 예제에는 없음)- 만약 함수 시그니처가
void some_function(std::array<std::uint32_t, N> arr)
였다면, 함수가 호출될 때my_array
의 완전한 복사본이 생성되어arr
에 전달됩니다. - 함수 내에서
arr
의 내용을 아무리 변경해도 원본인my_array
에는 아무런 영향이 없습니다. int
,double
같은 작은 기본 타입에는 유용하지만,std::array
나std::vector
같은 큰 컨테이너에는 복사 비용 때문에 거의 사용되지 않습니다.
- 만약 함수 시그니처가
예제 코드
#include <array>
#include <cstdint>
#include <iostream>
// const 참조 전달: 입력 전용이며, 복사 비용이 없고, 원본 수정이 불가능하다.
template <std::size_t N>
void print_array_values(
const std::array<std::uint32_t, N> &arr)
{
for (std::size_t i = 0; i < arr.size(); i++)
{
std::cout << arr[i] << '\n';
}
}
// 비-const 참조 전달: 입력과 출력이 모두 가능하며, 원본 수정이 목적이다.
template <std::size_t N>
void double_value(
std::array<std::uint32_t, N> &arr)
{
for (std::size_t i = 0; i < arr.size(); i++)
{
arr[i] = arr[i] * 2;
}
}
int main()
{
auto my_array = std::array<std::uint32_t, 5U>{1, 2, 3, 4, 5};
std::cout << "--- Initial Values ---\n";
print_array_values(my_array); // const 참조로 전달 (복사 없음)
double_value(my_array); // 비-const 참조로 전달 (원본 수정)
std::cout << "\n--- Values after doubling ---\n";
print_array_values(my_array); // const 참조로 전달 (복사 없음)
return 0;
}
실행 결과
--- Initial Values ---
1
2
3
4
5
--- Values after doubling ---
2
4
6
8
10
활용팁
- 읽기 전용(Input-only) 파라미터: 객체가 크거나 복사 비용이 비싸다면 항상
const
참조를 사용하세요.int
나double
같은 기본 타입(primitive types)은 크기가 작아 값으로 전달해도 무방합니다. - 수정 가능(Input/Output) 파라미터: 함수가 원본 객체를 수정해야 한다면 비-
const
참조를 사용하세요. 포인터를 사용할 수도 있지만, 참조가 일반적으로 더 안전하고 사용하기 쉽습니다. (예:nullptr
체크 불필요) - 소유권 이전: C++11 이후로는
std::move
와 r-value 참조를 통해 객체의 소유권을 효율적으로 이전하는 방법도 있습니다. 이는 복사를 피하면서 객체를 전달하는 또 다른 중요한 메커니즘입니다. (관련 문서: l-value/r-value,std::move
)
'개발 > C++ (98,03,11,14,17,20,23)' 카테고리의 다른 글
Modern C++ : string vs std::string vs std::array<char, N> (98, 11, 17) (1) | 2025.08.11 |
---|---|
Modern C++ : array vs std::array (98, 11) (1) | 2025.08.10 |
Modern C++ : anonymous namespace (98, 11, 17) (2) | 2025.08.09 |
Modern C++ : C-Style static function (98, 11) (1) | 2025.08.08 |
Modern C++ : static local variables (98, 11) (1) | 2025.08.07 |
Modern C++ : const vs constexpr (98, 11, 14, 17, 20) (0) | 2025.08.06 |
Modern C++ : type conversions (11, 14, 17, 20) (4) | 2025.08.05 |
Modern C++ : designated initializers (20) (0) | 2025.08.04 |