티스토리 뷰
내부에서 사용하는 객체에 대한 ‘핸들’을 반환하는 코드는 되도록 피하자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
//점을 나타내는 클래스
class Point
{
public:
void setX(int newVal);
void setY(int newVal);
};
//Rectangle에 쓰기 위한 점 데이터
struct RectData
{
Point ulhc; //ulhc = “좌측 상단(upper left – hand corner)”
Point lrhc; //lrhc = “우측 하단(lower right – hand corner)”
};
class Rectangle
{
private:
std::tr1::shared_ptr<RectData>pData; //tr1::shared_ptr 자원관리 클래스(스마트 포인터)
};
|
cs |
Point가 사용자 정의 타입 입니다.
사용자 정의 타입을 전달할 때는 값에 의한 전달 보다 참조에 의한 전달방식을 쓰는 편이 더 효율적 입니다.
그래서 이들 두 멤버 함수는 스마트 포인터로 물어둔 Point 객체에 대한 참조자를 반환하는 형태로 만들어졌습니다.
1
2
3
4
5
6
|
class Rectangle
{
public:
Point& upperLeft()const { return pData->ulhc; }
Point& lowerRight()const { return pData->lrhc; }
};
|
cs |
조금만 들여다보면 자기모순적인 코드임을 알 수 있습니다.
upperLeft 함수와 lowerRight 함수가 상수 멤버 함수 입니다.
하지만 호출부에서 private 멤버인 내부 데이터에 대한 참조자를 써서 내부 데이터를 맘대로 수정해도 좋다는 뜻이 됩니다.
1
2
3
4
5
6
7
8
9
10
11
|
void main()
{
Point coord1(0, 0);
Point coord2(100, 100);
//rec은 (0, 0)부터 (100, 100)의 영역에 있는 상수 Rectangle 객체 입니다.
const Rectangle rec(coord1, coord2);
//이제 이 rec은(50, 0)부터 (100, 100)의 영역에 있게 됩니다.
rec.upperLeft().setX(50);
}
|
cs |
rec은 상수 객체로 선언 되었지만 upperLeft를 호출한 쪽은 rec의 은밀한 곳에 숨겨진 Point 데이터 멤버를 참조자로 끌어와 바꿀 수 있다는 것입니다.
여기서 두 가지 교훈을 얻을 수 있습니다.
1. 클래스 데이터 멤버는 참조자를 반환하는 함수들의 최대 접근도에 따라 캡슐화가 정해진다는 점 입니다.
Ulhc와 lrhc는 private로 하는 upperLeft 및 lowerRight 함수가 public 멤버 함수이기 때문입니다.
2. 어떤 객체에서 호출한 상수 멤버 함수의 참조자 반환 값의 실제 데이터가 그 객체의 바깥에 저장되어 있다면,
이 함수의 호출부에서 그 데이터의 수정이 가능하다는 점입니다.
참조자, 포인터 및 반복자는 모두 핸들이고, 객체의 내부요소에 대한 핸들을 반환하게 만들면
언제든지 그 객체의 캡슐화를 무너뜨리는 위험과 상수 멤버 함수조차도 객체 상태의 변경을 허용하는 지경까지 이를 수 있습니다.
해결
호출부에서 객체의 상태를 바꾸지 못하도록 컴파일러 수준에서 막고 있는 방법으로 const 키워드 입니다.
1
2
3
4
5
6
7
|
class Rectangle
{
public:
const Point& upperLeft() const {return pData->ulhc;}
const Point& lowerRight() const {return pData->lrhc;}
};
|
cs |
이렇게 설계하면 사용자는 사각형을 정의하는 꼭짓점 쌍을 읽을 수는 있지만 쓸 수는 없게 됩니다.
사용자들이 Rectangle을 구성하는 Point를 들여다보도록 하자는 것은 처음부터 알고 시작한 설계이기 때문에 이 부분은 의도적인 캡슐화 완화라고 할 수 있습니다.
upperLeft 함수와 lowerRight 함수를 보면 내부 데이터에 대한 핸들을 반환하고 있는 부분이 있는데 이것을 남겨두면 다른 쪽에서 문제가 될 수 있습니다.
가장 큰 문제가 무효참조 핸들로서, 핸들이 있기는 하지만 그 핸들을 따라갔을 때 실제 객체의 데이터가 없는 것입니다.
1
2
3
4
5
6
7
8
9
10
11
|
class GUIObject { … };
//Rectangle 객체를 값으로 반환 합니다. 반환 타입에
// const가 붙은 이유는 반환타입을 상수화 시키는 것입니다.
const Rectangle Boundingbox(const GUIObject& obj);
이 상태에서 어떤 사용자가 이 함수를 사용한다고 생각해 보면
GUIObject *pgo; //pgo를 써서 임의의 GUIObject를 가리키도록 합니다.
//pgo가 가리키는 GUIObejct 의 사각 테두리 영역으로부터 좌측 상단 꼭짓점의 포인터를 얻습니다.
const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());
|
cs |
boundingBox 함수를 호출하면 Rectangle 임시 객체가 새로 만들어 집니다.
이 객체를 temp라고 가정하였을 때 이 temp에 대해 upperLeft가 호출될 텐데 이 호출로 인해 temp의 내부데이터,
두 Point 객체 중 하나에 대한 참조자가 나옵니다.
마지막으로 이 참조자에 &연산자를건 결과 값이 pUpperLeft 포인터에 대입되는 것입니다.
이 문장이 끝날 무렵 temp객체가 소멸되니 그 안에 들어 있는 Point 객체들도 덩달아 없어질 것입니다.
이 문장은 pUpperLeft에게 객체를 달아 줬다가 주소 값만 남기고 몽땅 빼앗아 간 것입니다.
객체의 내부에 대한 핸들을 반환하는 함수는 어떻게든 위험하다는 말이 이래서 나오는 것입니다.
포인터, 참조자, 반복자가 핸들을 반환하는 함수라는 사실만으로 위험하다는 것입니다.
일단 바깥으로 떨어져 나간 핸들은 그 핸들이 참조하는 객체보다 더 오래 살 위험이 있기 때문입니다.
결론
어떤 객체의 내부요소에 대한 핸들(참조자, 포인터, 반복자)을 반환하는 것은 되도록 피하는 것이 캡슐화 정도를 높이고,상수 멤버 함수가 객체의 상수성을 유지한 채로 동작할 수 있도록 하며, 무효참조 핸들이 생기는 경우를 최소화할 수 있습니다.
- Total
- Today
- Yesterday
- 현포다이브
- 성산블루버블
- C#.NET
- 블루버블다이빙팀
- 울릉도
- 블루버블다이브팀
- 티스토리챌린지
- 암호화
- Linux
- C
- 블루버블
- DLL
- PowerShell
- C++
- 제주도
- 서귀포
- 오블완
- C#
- 외돌개
- 서귀포블루버블
- OpenSource
- script
- C# 고급 기술
- CMake
- 네트워크 정보
- 패턴
- Build
- 스쿠버 다이빙
- 스쿠버다이빙
- Windows
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |