KoreanFoodie's Study

Effective C++ | 항목 28 : 내부에서 사용하는 객체에 대한 '핸들'을 반환하는 코드는 되도록 피하자 본문

Tutorials/C++ : Advanced

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

GoldGiver 2022. 10. 25. 16:24

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

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

핵심 :

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


클래스 내부의 데이터를 참조자로 반환하는 것은 주의해야 한다. 다음 예시를 보자.

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

struct RectData
{
  Point ulhc; // upper left-hand corner
  Point lrhc; // lower right-hand corner
};

class Rectangle
{
  public:
    // !참조자로 Point 객체를 반환!
    Point& upperLeft() const { return pData->ulhc; }
    Point& lowerRight() const { return pData->lrhc; }
  
  private:
    std::shared_ptr<RectData> pData;
};

...
Point coord1(0, 0);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2);

// 상수 객체인 rec 의 내부 데이터를 수정했다!
rec.upperLeft().setX(50);

Rectangle 클래스의 upperLeft 함수와 lowerRight 함수를 보면, Point 를 참조자로 반환하고 있다. 이 때문에, 상수 객체인 rec 의 데이터 멤버의 값이 수정이 된다.
ulhc 와 lrhc 는 private 으로 선언되어 있는데, 실질적으로는 참조자를 반환하는 함수로 인해 수정이 가능해지므로, public 변수나 다를 바가 없어진다. 이는 참조자 말고도 포인터나 반복자를 반환하는 경우에도 비슷한 문제가 발생할 것이다.

위 문제는 사실 다음과 같이 간단하게 해결할 수는 있다.

class Rectangle
{
  public:
    // 참조자로 Point 객체를 반환하지만, const 임
    const Point& upperLeft() const { return pData->ulhc; }
    const Point& lowerRight() const { return pData->lrhc; }
    ...
};

하지만 위처럼 핸들을 반환하는 경우는, 실제 핸들이 가리키는 녀석이 사라지는 무효참조 핸들(dangling handle) 문제가 발생할 수 있다.

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

GUIObject *pgo;
...
const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());

위의 경우, boundingbox 는 Rectangle 임시 객체(temp 라고 부르자)를 만든다. 이 temp 객체는, uppderLeft 를 통해 ulhc 를 뱉어 내고, 해당 값의 주소를 pUpperLeft 에게 전달한다. 하지만 boundBox 를 호출하는 문장이 끝날 때 temp 객체는 파괴되기 때문에, pUpperLeft 는 실제 객체가 날라간 주소 값을 가지고 있게 된다!
핸들을 반환하는 멤버 함수가 필요한 경우도 있다(operator[] 연산자 등). 하지만 이런 경우는 예외적인 경우이므로, 일반적으로는 '핸들'을 반환하는 코드는 '되도록 피하도록' 하자.

Comments