[C++] 항목 21: 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자
카테고리: Cpp
태그: Cpp
이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역
📦 4. 설계 및 선언Permalink
👉🏻 항목 21: 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자Permalink
‘값에 의한 전달’이 성능 상 좋지 않다는 이유로, ‘참조에 의한 전달’을 고집하는 것이 좋지 않은 경우가 있다.
아래 코드를 살펴보자.
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
...
private:
int n, d; // 분자, 분모
friend const Rational operator*(const Rational& lhs, const Rational& rhs);
};
이 클래스는 유리수를 표현하고, 곱셈 연산을 정의한다.
곱셈 연산자는 ‘값에 의한 반환’을 하고 있는데, 누군가는 이 비용이 아깝다고 생각할 수 있다.
그래서 아래와 같은 방식으로 ‘참조 반환’ 을 시도할 수도 있다.
➡️ ‘참조에 의한 전달’ 구현 시도Permalink
1. 스택에 객체 생성 후 참조 반환Permalink
const Rational& operator*(const Rational& lhs, const Rational& rhs) {
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
❌ 문제점
result
는 지역 객체라 함수 종료 시 소멸된다.- 즉, 소멸된 객체를 참조하고 있는 꼴이 된다.
2. 힙에 객체 생성 후 참조 반환Permalink
const Rational& operator*(const Rational& lhs, const Rational& rhs) {
Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return *result;
}
❌ 문제점
- 힙 객체는 수동으로 소멸시켜야 한다.
-
하지만
w = x * y * z
같은 식에서는 중간에 생성된 객체를 어디서 어떻게 delete 할지 알 수 없다.→ 결국 메모리 누수로 이어진다.
3. 정적 객체 반환Permalink
const Rational& operator*(const Rational& lhs, const Rational& rhs) {
static Rational result;
result = ... // 연산 수행
return *result;
}
❌ 문제점
- 정적 객체는 하나만 존재한다.
a*b == c*d
같은 비교 연산을 하면, 모든 결과가 같은 객체를 가리키기 때문에 항상 같다고 판단할 수도 있다.- 게다가 다중 스레드 상황에서는 데이터 레이스가 발생한다.
3.1. 정적 배열 사용Permalink
❌ 문제점
- 배열의 크기를 미리 정해야 한다.
- 크기가 작으면 공간 부족, 크면 성능 저하
- 호출될 때마다 n개의 객체가 생성되고 파괴됨 → 오히려 낭비
✅ 해결법Permalink
inline const Rational operator*(const Rational& lhs, const Rational& rhs) {
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
→ 그냥 ‘값에 의한 반환’을 사용하면 된다.
현대 컴파일러는 RVO(Return Value Optimization) 와 move semantics 덕분에 이 방식이 오히려 더 깔끔하고 효율적이다.
🧐 정리Permalink
- 아래의 방식은 사용하지 말자:
- 스택 객체의 참조자나 포인터를 반환
- 힙 객체의 참조자 반환 (소멸 시점을 제어할 수 없음)
- 정적 객체 또는 배열을 활용한 참조자 반환 (동시성, 재사용 문제)
→ 결국에는 모두 의도치 않은 부작용이나 버그로 이어진다.
그러니 객체 반환이 필요하면, 값에 의한 반환을 주저하지 말자.
댓글남기기