728x90
반응형

예시로 사각형 클래스로부터 시작해보자.

메모리 효율을 높이기 위해 꼭짓점(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 [] 연산자와 같이 특별하게 필요한 경우가 아니라면 반드시 피하도록 하자.

 

요약

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