[Effective C++] 28. 내부에서 사용하는 객체에 대한 '핸들'을 반환하는 코드는 되도록 피하자
예시로 사각형 클래스로부터 시작해보자.
메모리 효율을 높이기 위해 꼭짓점(Point)을 별도의 구조체(RectData)에 넣어 사각형 클래스(Rectangle)가 이를 가리키도록 해보자.
class Point
{
public:
Point(int x, int y);
void setX(int newVal);
void setY(int newVal);
};
struct RectData
{
Point ulhc; // 좌측상단
Point lrhc; // 우측하단
};
class Rectangle
{
private:
std::tr1::shared_ptr<RectData> pData;
public:
// 아래 두행은 전부 상수멤버함수임을 기억하자.
Point & upperLeft() const { return pData->ulhc; } // 좌측상단 값 get
Point & lowerRight() const { return pData->lrhc; } // 우측하단 값 get
};
위의 코드를 보면 상수 멤버 함수로 upperLeft(), lowerRight()를 선언하였다. 상수 객체함수를 통해 호출객체가 수정되지 않게하기위함않게 하기 위함( = 상수성을 유지하기 위해)이 그 이유이다.
하지만 이 함수들이 반환하는 게 private 멤버인 내부 데이터에 대한 참조자여서 아래와 같이 사용했을 때 상수 객체가 참조하는 데이터가 수정돼버린다.
Point coord1(0,0);
Point coord2(100,0);
const Rectangle rec(coord1, coord2);
rec.upperLeft().setX(50); // 수정됨
여기서 2가지 교훈을 얻을 수 있는데,
1. 데이터 멤버(ex.ulhc, lrhc)를 private로 선언한다 하더라도 그 멤버의 참조자를 반환하는 함수(ex. upperLeft(), lowerRight()가 public => 데이터 멤버의 접근도가 private이라도 해당 함수로 인해 접근도가 변경됨)들의 접근도에 따라 그 캡슐화 정도가 정해진다.
2. 어떤 객체에서 호출한 상수 멤버 함수(ex. Point& upperLeft() const )의 참조자반환 값의 실제 데이터가 해당 객체의 바깥에저장되어 있다면 이 함수의 호출부에서 그 데이터가 수정이 가능하다.
이러한 교훈을 통해 '핸들(다른 객체에 손댈 수 있는 매개자 : 참조자, 포인터, 반복자)을 반환하는 코드는 최대한 피하자.'는 것을 알 수 있다.
해당 문제를 해결하는 방법으로는 상수 멤버 함수 upperLeft()와 lowerRight()의 반환 타입에 const를 붙여 상태 변경을 금지시키는 방법이 있다.
class Rectangle
{
public:
const Point& upperLeft() const{ return pData->ulhc; }
const Point& lowerRight() const{ return pData->lrhc; }
};
다음과 같이 const를 앞에 붙여주면 핸들을 반환해도 그 값을 변경할 수는 없기 때문에 해결된다. 또한 캡슐화 문제라면 이 클래스는 의도적으로 Point를 들여다보도록 설계한 것이기 때문에 의도적인 캡슐화 완화라고 볼 수 있다.
하지만 완벽하게 해결한 것은 아니다. 또 다른 문제가 발생할 수 있기 때문이다.
무효 참조 핸들
무효 참조 핸들이란 그 핸들이 가리키는 데이터를 따라갔을 때 해당 데이터가 없는 것으로 아래의 예시를 보자.
class GUIObject{};
const Rectangle boundingBox(const GUIObject& obj);
GUIObject *pgo;
const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());
GUI 객체의 사각 테두리 영역을 Rectangle 객체로 반환하는 함수가 있다고 하자. boundingBox를 호출하면 임시 객체가 생성되는데 이 임시 객체에 대해 upperLeft가 호출되며 두 Point 객체 중 하나에 대한 참조자가 나온다. 그리고 그 주소 값이 pUpperLeft에 반환되는 형식이다. 그러나 이 문장이 끝날 무렵 임시 객체는 소멸되기 때문에 pUpperLeft가 가리키는 객체는 날아가고 없게 된다.
따라서 "핸들을 반환하는 것"을 반드시 되도록 피해야 하며, 어떻게든 문제가 발생할 수 있는 여지가 있기 때문에 operator [] 연산자와 같이 특별하게 필요한 경우가 아니라면 반드시 피하도록 하자.
요약
- 어떤 객체의 내부 요소에 대한 핸들(참조자, 포인터, 반복자)을 반환하는 것은 되도록 피해야 한다. 캡슐화 정도를 높이고, 상수 멤버 함수가 객체의 상수성을 유지한 채로 동작할 수 있도록 하며, 무효 참조 핸들이 생기는 경우를 최소화할 수 있다.
'Books > Effective C++' 카테고리의 다른 글
[Effective C++] 30. 인라인 함수는 미주알고주알 따져서 이해해 두자 (0) | 2022.06.19 |
---|---|
[Effective C++] 29. 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자! (0) | 2022.06.19 |
[Effective C++] 27. 캐스팅은 절약, 또 절약! 잊지 말자 (0) | 2022.06.18 |
[Effective C++] 26. 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자 (0) | 2022.06.18 |
[Effective C++] 25. 예외를 던지지 않는 swap에 대한 지원도 생각해 보자 (0) | 2022.06.12 |
댓글
이 글 공유하기
다른 글
-
[Effective C++] 30. 인라인 함수는 미주알고주알 따져서 이해해 두자
[Effective C++] 30. 인라인 함수는 미주알고주알 따져서 이해해 두자
2022.06.19 -
[Effective C++] 29. 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자!
[Effective C++] 29. 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자!
2022.06.19 -
[Effective C++] 27. 캐스팅은 절약, 또 절약! 잊지 말자
[Effective C++] 27. 캐스팅은 절약, 또 절약! 잊지 말자
2022.06.18 -
[Effective C++] 26. 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자
[Effective C++] 26. 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자
2022.06.18