[C++] 항목 28: 내부에서 사용하는 객체에 대한 ‘핸들’을 반환하는 코드는 되도록 피하자

업데이트:     Updated:

카테고리:

태그:

이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역

📦 5. 구현

👉🏻 항목 28: 내부에서 사용하는 객체에 대한 ‘핸들’을 반환하는 코드는 되도록 피하자

✅ 문제 상황: 핸들 반환으로 인한 캡슐화 파괴

다음은 좌표와 사각형을 나타내는 클래스이다:

class Point {
public:
	Point(int x, int y);
	...
	void setX(int newVal);
	void setY(int newVal);
	...
};

struct RectData {
	Point ulhc; // 좌측 상단
	POint lrhc; // 우측 하단
};

class Rectangle {
public:
	...
	// 값에 의한 전달이 아닌 참조에 의한 전달을 사용하였다.
	Point& upperLeft() const { return pData->ulhc; }
	Point& lowerRight() const { return pData->lrhc; }
	...

private:
	shared_ptr<RectData> pData;
};

이 코드를 사용하면 다음과 같은 문제가 발생한다:

int main() {
	Point coord1(0, 0);
	POint coord2(100, 100);

	const Rectangle rec(coord1, coord2);
	rec.upperLeft().setX(50);
}

문제점:

  1. 캡슐화 파괴: 클래스 데이터 멤버는, 그 멤버의 참조자를 반환하는 함수들의 최대 접근도에 따라 캡슐화 정도가 정해진다. ulhc, lrhc는 private 멤버이지만, 이들의 참조자를 반환하는 함수(upperLeft, lowerRight)가 public에 있기 때문이다.
  2. 상수성 위반: 어떤 객체에서 호출한 상수 멤버 함수의 참조자 반환 값의 실제 데이터가 그 객체의 바깥에 저장되어 있다면, 이 함수의 호출부에서 그 데이터의 수정이 가능하다. upperLeft, lowerRight를 통해 얻어낸 포인터가 외부에 있으면 수정이 가능해진다.

✅ 첫 번째 해결 시도: const 참조 반환

문제 2번을 해결하기 위해 다음과 같이 수정할 수 있다:

class Rectangle {
public:
	...
	const Point& upperLeft() const { return pData->ulhc; }
	const Point& lowerRight() const { return pData->lrhc; }
	...
};

이와 같이 반환 타입에 const만 붙여주어도 해결된다.


✅ 더 큰 문제: 무효참조 핸들(dangling handle)

이렇게 고쳤음에도 가장 큰 문제가 생긴다.

무효참조 핸들(dangling handle): 핸들이 있지만, 핸들을 따라가면 실제 객체의 데이터가 없는 것

class GUIObject { ... };
const Rectangle boundingBox(const GUIObject& obj);

int main() {
	GUIObject* pgo;
	...
	// 아래 줄에서 문제 발생
	const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());
}

문제 분석:

  • boundingBox 함수는 값을 반환한다
  • 그러므로 해당 줄이 끝나는 동시에 Rectangle 객체와 안에 들어 있는 Point 객체들도 동시에 소멸된다
  • 이의 결과로, pUpperLeft는 무효참조 핸들이란 문제를 안게 된다

✅ 핵심 원칙

결론적으로, 객체의 내부에 대한 핸들을 반환하는 함수는 위험하다.

  • 포인터이든, 참조자든, 반복자든 상관없다
  • 핸들에 const를 붙였든 상관없다
  • 핸들을 반환하는 함수라는 사실 자체가 위험하다

그렇다고 핸들을 반환하는 멤버 함수를 사용하지 말자는 것은 아니고, ‘피하자’이다. 어쩌다보면 필요한 경우가 있다.


🧐 정리

  1. 어떤 객체의 내부요소에 대한 핸들(참조자, 포인터, 반복자)을 반환하는 것은 되도록 피하자.
  2. 캡슐화 정도를 높이고, 상수 멤버 함수가 객체의 상수성을 유지한 채로 동작할 수 있도록하고, 무효참조 핸들이 생기는 경우를 최소화할 수 있다.

Cpp 카테고리 내 다른 글 보러가기

댓글남기기