핵심 정리
std::string name = "Mina";
std::string& alias = name; // 수정 가능한 참조
const std::string& view = name; // 읽기 전용 참조
void print(const std::string& s); // 복사 없이 읽기
void append(std::string& s, char c); // 원본 수정참조는 포인터보다 "별칭" 감각이 강하고, 함수 인자에서는 값 복사와 원본 공유를 나누는 기본 도구입니다.
문법
참조 카드에서 먼저 다시 보는 기본형은 아래입니다.
- 수정 가능한 별칭:
T& - 읽기 전용 참조:
const T& - 이동 전용 문맥:
T&& - 값 전달:
T
참조는 별칭이다 — 포인터와의 차이
참조는 기존 객체에 새 이름을 붙이는 것입니다. 포인터와 달리 초기화 이후 다른 객체를 가리키게 변경할 수 없고, nullptr이 될 수 없습니다. 역참조 연산자(*) 없이 객체처럼 직접 씁니다.
int x = 10;
int& r = x; // r은 x의 별칭
r = 20; // x = 20 과 동일
int y = 30;
r = y; // ❌ r이 y를 가리키게 변경되지 않음 — x에 30이 복사됨
// 참조는 재바인딩 불가
// 포인터는 재지정 가능
int* p = &x;
p = &y; // ✅ p가 y를 가리킴const T& — 함수 인자의 기본 선택
큰 객체를 함수에 넘길 때 값으로 전달하면 복사가 일어납니다. const T&는 복사 없이 읽기만 하겠다는 약속입니다. 임시 객체(rvalue)도 const T&에 바인딩되므로 문자열 리터럴도 받을 수 있습니다.
// ❌ 값 전달 — Student 복사 발생
void print_score(Student s) { ... }
// ✅ const 참조 — 복사 없이 읽기
void print_score(const Student& s) { std::cout << s.score; }
// const T&는 임시 객체도 받음
print_score(Student{"temp", 90}); // ✅ rvalue도 바인딩T& — 원본 수정이 필요할 때
함수 안에서 인자의 원본을 수정해야 한다면 T&(비 const 참조)로 받습니다. 함수 시그니처에서 "이 인자는 수정됩니다"라는 계약이 명시됩니다.
void normalize(std::vector<double>& v) {
double max = *std::max_element(v.begin(), v.end());
for (auto& x : v) x /= max;
}
std::vector<double> data{3.0, 1.0, 2.0};
normalize(data); // data 원본이 수정됨값 전달과 참조 전달은 복사 여부가 다르다
void set_zero_copy(int value) {
value = 0;
}
void set_zero_ref(int& value) {
value = 0;
}
int x = 5;
set_zero_copy(x); // x는 그대로
set_zero_ref(x); // x가 0으로 바뀜겉으로는 둘 다 "인자를 받는 함수"지만, 하나는 복사본을 받고 하나는 원본을 바로 만집니다. 함수 계약을 읽을 때 &가 붙었는지 먼저 보는 이유가 여기 있습니다.
체크포인트
- 함수에서 읽기만:
const T& - 함수에서 원본 수정:
T& - 복사본이 필요하다:
T - 이동 가능한 임시 객체를 구분한다:
T&& - 멤버 변수로 참조를 저장할 때는 수명부터 확인
주의할 점
지역 변수의 참조를 함수 밖으로 반환하면 수명이 끝난 객체를 가리키는 댕글링 참조가 됩니다.
// ❌ 지역 변수 참조 반환 — dangling reference
std::string& get_name() {
std::string local = "temp";
return local; // local은 함수 종료 후 소멸
}
auto& name = get_name(); // UB — 소멸된 객체 참조
// ✅ 값을 반환하거나, 수명이 충분한 객체의 참조를 반환
std::string get_name_val() {
return "temp"; // 값 반환 — NRVO로 복사 없이 최적화
}
// ✅ 인자로 받은 참조를 그대로 반환 — 수명은 호출자가 관리
const std::string& longer(const std::string& a, const std::string& b) {
return a.size() >= b.size() ? a : b;
}// ❌ const가 빠지면 임시 객체를 받기 어렵고, 읽기 전용 의도도 흐려진다
void print_name(std::string& name);
// ✅ 읽기만 하는 기본 인자는 const reference가 더 자연스럽다
void print_name_ok(const std::string& name);참고 링크
1 sources