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

수민이슈·2023년 4월 3일
0

Effective C++

목록 보기
28/30
post-thumbnail

스콧 마이어스의 Effective C++을 읽고 개인 공부 목적으로 요약 작성한 글입니다!

💡 어떤 객체의 내부요소에 대한 핸들(참조자, 포인터, 반복자)를 반환하는 것은 되도록 피하도록 하자!

  • 캡슐화 정도를 높일 수 있다
  • 상수 멤버 함수가 객체의 상수성을 유지한 채로 동작할 수 있도록 한다
  • 댕글링 핸들(무효 참조 핸들)이 생기는 경우를 최소화 할 수 있다

🖊️ 핸들을 반환하는 코드

예시

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:
	std::tr1::shared_ptr<RectData> pData;
};

이 상황을 보면

upperLeft(), lowerRight() 함수는 상수 멤버 함수인데
스마트 포인터로 묶은 Point 객체에 대한 참조자를 반환하고 있다.

당연함
사용자 정의 데이터형을 전달할 때는 에 의한 전달보다 참조자에 의한 전달이 더 효율적이니까.

근데
private 멤버인 내부 데이터에 대한 참조자를 반환하고 있으니
호출부에서 이 참조자를 통해서 내부 데이터를 변경할 수도 있겠다

아놔

Point coord1(0, 0);
Point coord2(100, 100);
const Rectangel rec(coord1, coord2);
rec.upperLeft().SetX(50);

이렇게 해버리면
rec이 (0, 0) ~ (100, 100)에서 (50, 0) ~ (100, 0)으로 영역이 바뀔거다
const인데?
상수인데?

교훈

클래스 데이터 멤버의 참조자를 반환하는 함수의 접근도에 따라 캡슐화 정도가 정해진다.

긍까
ulhc, lrhc는 private 멤버인데
얘네가 private으로 선언이 되어 있어도
public으로 선언된 함수에서 얘네에 대한 참조자를 반환하므로
얘네도 결국은 public 멤버라고 봐야 한다는 거다.
백날 private으로 보호했다고 빽빽 우겨봐라
public이지..

어떤 객체에서 호출한 상수 멤버 함수의 참조자 반환 값의 실제 데이터가 그 객체의 바깥에 저장되어 있다면, 이 함수의 호출부에서 그 데이터의 수정이 가능하다

이게 책에 나와있는 말 그대로인데,
좀 문장이 길지만 이해하면 쉽다.
private으로 선언한 데이터 값이라도
참조자가 반환해서 저장된다면
객체도 있으니 그냥 변경해버릴 수 있다.


🖊️ 무효참조 핸들(dangling handle) 문제

핸들(handle)

핸들은
다른 객체에 손을 댈 수 있게 해주는 매개체 역할을 하는 것을 의미한다
예를들면
포인터, 참조자, 반복자.

어떤 객체의 내부 요소(private, protected로 선언된 것)를 반환하게 만들면,
언제든지 그 객체의 캡슐화를 🐙뜨릴 수 있게 된다.

그래서

private, protected로 선언된 내부 객체에 대해서는 포인터를 반환해서는 안된다.
이 객체의 포인터를 반환하는 멤버함수도 만들면 안된다.
public 멤버 함수가 얘네 내부 객체의 핸들을 반환하면
결국 내부 객체도 public이 되어 버린다는 뜻이다.

이럴 때,,

반환 타입에 const를 붙이면 읽기O 쓰기X 아님??

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

이렇게 하면
반환 타입에 const가 붙어서
호출부에서 객체의 상태를 못바꾸도록 컴파일러수준에서 막을 수 있지 않을까 ? ㅎㅎ
요러고있음

그래 맞긴함
읽기 접근만 가능하고
쓰기 접근은 안되긴 함

근데,, 이 때 발생할 수 있는 문제가 무효참조 핸들이다.

무효 참조 핸들

핸들이 있지만, 핸들이 가리키는 실제 객체의 데이터가 없는 경우를 의미한다.

함수가 객체를 으로 반환했을 때 흔히 발생하게 된다.

class GUIObject { ... };

const Rectangle boundingBox(const GUIObject& obj);

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

이 상황에서
boundingBox()를 호출했을 때 생기는 Rectangle 임시 객체 temp가 있을거고
얘에 대해 upperLeft()가 호출된다.
그러면 temp의 Point 객체의 참조자가 반환되어서 pUpperLeft에 대입된다

근데 이제
temp는 함수 안에서 생기니까
소멸되어야 할거고
얘가 소멸되면서 Point 객체도 소멸될거고
그러면
pUpperLeft가 가리키는 참조자의 객체는
...?

사라졋습니다.

이게 무효 참조 핸들 문제.

이걸로 골치 꽤나 먹었지!

정리

그니까
웬만하면 private, protected 객체에 대한 핸들을 반환하는 함수는 사용하지 말자

물론
vector, string 클래스에서 opeator[]
내부 객체에 대한 참조자를 반환하긴 하는데
컨테이너가 사라질 때 같이 사라지는 데이터이기도 하고
특수 상황이다

내가 직접 코딩할때는
그러지말장.


😊

댕글링 포인터 문제.. 정말 많이 당했던 문제였다
C++ 수업 들을 때 많이 강조되기도 했고

0개의 댓글