참조(reference)
개념
참조(reference)는 C++에서 도입된 개념으로, 이미 존재하는 변수에 대한 별칭(alias) 을 생성하는 기능이다. 참조는 선언 시점에 반드시 초기화되어야 하며, 한번 초기화된 후에는 다른 객체를 참조할 수 없다.
int original = 5; // 원본 변수
int& ref = original; // original에 대한 참조 변수 선언
ref = 10; // original 값도 10으로 변경됨
포인터와의 차이점
초기화 필수성
- 참조: 선언과 동시에 반드시 초기화해야 한다.
- 포인터: 선언 시 초기화하지 않아도 됨
재할당 가능성
- 참조: 한번 초기화되면 다른 객체를 참조할 수 없음
- 포인터: 다른 주소를 가리키도록 변경 가능
NULL 값
- 참조: NULL 참조 없음(항상 유효한 객체를 참조해야 함)
- 포인터: NULL 포인터 가능
연산자 사용
- 참조: 원본 변수와 동일하게 직접 접근(
ref = 10;
) - 포인터: 역참조 연산자(
*
)를 사용(*ptr = 10;
)
메모리 할당
- 참조: 일반적으로 별도의 메모리를 할당하지 않음
- 포인터: 포인터 자체에 메모리가 할당됨
참조 매개변수와 반환 값
참조 매개변수
함수에 인자를 전달할 때 참조를 사용하면 값의 복사 없이 원본 데이터에 직접 접근할 수 있다. 이는 C에서 포인터를 사용하는 이유와 유사하지만, 문법적으로 더 간결하고 안전함
// 값에 의한 전달(pass by value) - 원본 변경 안됨
void incrementByValue(int num) {
num++; // 지역 변수만 변경됨
}
// 참조에 의한 전달(pass by reference) - 원본 변경됨
void incrementByReference(int& num) {
num++; // 원본 변수 값이 변경됨
}
참조 매개변수의 장점
- 큰 객체 전달 시 효율성: 큰 객체를 값으로 전달하면 복사 비용이 발생하지만, 참조로 전달하면 복사 없이 원본에 접근
- 원본 변경 가능: 함수 내에서 원본 데이터를 직접 수정할 수 있음
- 문법적 간결함: 포인터보다 사용하기 쉽고, 코드가 더 읽기 쉬움
참조 반환 값
함수가 참조를 반환하면 함수 호출 표현식이 l-value(변수처럼 값을 할당받을 수 있는 표현식)가 될 수 있다.
int& getElement(vector<int>& vec, int index) {
return vec[index]; // 벡터 요소에 대한 참조 반환
}
vector<int> numbers = {10, 20, 30};
// 함수 반환값에 직접 할당 가능
getElement(numbers, 1) = 50;
// numbers는 이제 {10, 50, 30}
참조 반환의 주의사항
- 지역 변수 참조 반환 금지: 함수 내 지역 변수의 참조를 반환하면 함수 종료 후 해당 변수는 소멸되어 댕글링 참조(dangling reference)가 발생
- 반환 값의 생명주기: 참조로 반환된 객체의 생명주기가 함수 호출보다 길어야 함
const 참조의 활용
const 참조의 기본 개념
const
참조는 참조를 통해 원본 객체를 수정할 수 없도록 하는 기능이다. 이는 함수 매개변수로 자주 사용되어 원본 데이터의 무결성을 보장한다.
void printData(const int& data) {
// data = 100; // 오류: const 참조를 통해 값을 변경할 수 없음
cout << data << endl; // 읽기만 가능
}
const 참조의 특별한 성질
일반 참조와 달리, const
참조는 다음과 같은 특별한 성질을 가진다.
임시 객체(r-value)에 대한 참조 가능
// 일반 참조는 임시 객체에 바인딩할 수 없음
// int& ref = 5; // 컴파일 오류
// const 참조는 임시 객체에 바인딩 가능
const int& ref = 5;
타입 변환 허용
double value = 3.14;
// int& ref = value; // 컴파일 오류: 타입 불일치
// const 참조는 타입 변환 허용
const int& ref = value; // 내부적으로 임시 int 객체 생성
const 참조의 주요 활용 사례
함수 매개변수
큰 객체를 복사 없이 전달하되 수정은 방지하고 싶을 때 사용
void processString(const string& str) {
// str의 내용은 읽을 수 있지만 수정할 수 없음
}
클래스의 멤버 함수
객체의 상태를 변경하지 않는 멤버 함수(getter 등)는 const
메소드로 선언하고, 내부에서 const
참조를 사용
class Data {
private:
vector<int> values;
public:
// const 멤버 함수에서 멤버 변수를 const 참조로 접근
const vector<int>& getValues() const {
return values; // 안전하게 내부 데이터 참조 반환
}
}
효율적인 범위 기반 for 루프
vector<string> names = {"Alice", "Bob", "Charlie"};
// 효율적인 읽기 전용 반복
for (const string& name : names) {
cout << name << endl;
// name = "Modified"; // 컴파일 오류: 수정 불가
}
참조의 실용적 활용 사례
값과 참조 중 선택 기준:
- 작은 기본 타입(int, double 등)은 값으로 전달
- 큰 객체나 사용자 정의 타입은 참조로 전달(const 참조 권장)
- 원본 수정이 필요한 경우에만 비-const 참조 사용
반환 값으로서의 참조
- 클래스 내부 멤버에 대한 접근자 함수 사용
- 연산자 오버로딩에서 체이닝을 위해 사용(
obj1 = obj2 = obj3
) - 지역 변수의 참조 반환은 절대 금지
r-value 참조와 이동 의미론
- C++11부터 도입된 r-value 참조(
&&
)는 임시 객체를 효율적으로 활용하기 위한 기능 - 이동 생성자와 이동 대입 연산자에서 주로 사용
class MyString {
public:
// 이동 생성자(r-value 참조 사용)
MyString(Mystring && other) noexcept {
// other의 리소스를 훔쳐옴
}
}
요약
C++의 참조는 포인터의 강력함과 일반 변수의 사용 편의성을 결합한 기능이다. 참조는 코드를 더 직관적이고 안전하게 만들어 주며, 특히 함수 매개변수와 반환 값에서 효율성과 가독성을 높여준다. const
참조의 활용은 C++의 핵심 기법 중 하나로, 큰 객체를 효율적으로 전달하면서도 무결성을 보장할 수 있게 해준다.