본문 바로가기

Effective C++/5. 구현

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

사각형(Rectangle) 사용하는 응용프로그램을 만드는 중이다.. 사각형은 좌측상단 꼭짓점과 우측하단 꼭짓점으로 나타낼 있다. 그리고 메모리 부담을 줄여야 한다. => 사각형의 영역을 정의하는 꼭짓점을 Rectangle 자체에 넣기 보다, 꼭짓점을 별도의 클래스(RecData)에 넣은 Rectangle 구조체를 가르키도록 했다.

 

 

 

사용자가 꼭짓점 정보가 필요할 때가 있으니 꼭짓점 정보를 반환하는 upperLeft() lowerRight() 선언했는데, const인데 Point&(꼭짓점에 대한 핸들) 반환했다. => 함수 자체에선 내부 정보를 안건드니까 const 해도 에러가 안뜨지만, 반환된 Point 핸들은 아무 제약이 없으므로, 실질적으로 데이터가 바뀔 있다.

 

이런 코드가 가능해짐

 

 

분명 rec const 선언되었는데, rec 내부의 꼭짓점 정보를 바꿀 있다!!

 

======================================================================

 

여기서 있는 사실

 

  • 클래스 데이터 맴버는 아무리 숨겨봤자(private으로 해봤자), 맴버의 핸들(참조자, 포인터, 반복자) 반환하는 함수들의 최대 접근도 따라 캡슐화 정도가 정해진다.

 

지금 경우에 빗대어 설명하자면, ulhc lrhc private 이지만 실질적으로는 public이다. 왜냐하면 이들의 참조자를 반환하는 upperLeft, lowerRight public 함수이기 때문이다. => 비트수준 상수성의 한계(항목 3 참고)

 

어떤 객채의 내부요소(internals) 데이터 맴버 + 외부에서 접근 불가능한(protected, private으로 선언된) 맴버 함수 이다. => 이런 맴버 함수에 대한 핸들도 반환하면 안된다. private, protected 되어 있는 어떤 것들에 대한 핸들도 반환하면 안된다.

 

그래서 upperLeft lowerRight 다음과 같이 바뀌어야

 

 

참조자가 아닌 const 참조자( 상수 객체) 반환하니 다음과 같은 시도가 먹히지 않는다.

이렇게 설계하면, 사용자는 사각형을 정의하는 꼭짓점 쌍을 읽을 수는 있지만, 수는 없게 된다. const 선언한게 드디어 효과를 발휘함

 

=> 읽기 접근은 허용하고, 쓰기 접근은 허용하지 않음 (읽기 접근의 의도적으로 허용함, 캡슐화를 의도적으로 느슨하게 했지만, 느슨하게 만드는 데에도 제한을 )

 

 

그래도 찝찝한게, upperLeft lowerRight 내부 데이터에 대한 핸들을 반환하는 부분이 남아 있긴 하다.( return 부분 ) => 문제가 생길 있다.

 

어떤 문제? => dangling handle ,  핸들을 따라갔더니 실제 객체가 없는 경우

 

이해가 안된다 => 예를 보자. 인터페이스를 그대로 사용하고, 어떤 GUI 객체의 사각 테투리 영역(bounding box) Rectangle 객체로 반환하는 함수가 있다고 생각해보자.

 

 

 

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

 

문장을 보자. boundingBox() 호출해서 rec 반환되었고,  rec 대한 upperLeft() 호출, rec->ulhc 반환되고 객체의 포인터가 반환될 것이다. 그리고 문장이 끝나면 rec 사라진다!!! => rec->ulhc 당연히 사라진다. => pUpperLeft dangling pointer 가지게 된다.

 

포인트에 getter 하나 추가하고

 

 

 

미정의 동작을 보이게 된다.( pUpperLeft 가르키는 객체가 사라졌기 때문이다.)

 

======================================================================

 

객체 내부에 대한 핸들을 반환하는 함수는 어떻게든 위험하다는 말이 이래서 나오는 것이다. 분명 upperLeft const 객체를 반환함에도 이런 일이 발생한다. 핸들이 참조자이든, 포인터이든, 반복자이든, 핸들에 const 붙였든 상관없다. 중요한 것은 핸들을 반환하는 함수인 자체가 중요하다. 일단 바깥으로 떨어져 나간 핸들은(반환된 핸들은) 핸들이 참조하는 객체보다 오래 위험이 있기 때문이다.(그래서 dangling handle 문제가 나오는 것이다.)

 

그래서 핸들을 반환하는 맴버함수를 절대로 두지마라는 얘기가 아니라, 되도록 피하자라는 이야기이다. 어쩌다 보면 핸들을 반환하는 함수가 필요할 때가 있다. 대표적인 경우가 operator[] 이다. 얘는 애초에 핸들을 반환하도록 설계된 연산자이기 때문이다. 하지만 경우는 원소를 저장하는 컨테이너가 사라질 원소도 같이 사라지기 때문에 dangling handle 문제가 일어나지 않는다. 하지만 이런 함수는 예외적이다.

 

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