22: 데이터 멤버가 선언될 곳은 private 영역임을 명심하자
우선 문법적 일관성이 첫 번째 이유이다. 데이터 멤버가 public이 아니라면, 사용자 족에서 어떤 객체를 접근할 유일한 수단은 멤버 함수이다. 사용자쪽에서는 접근하고 싶을 때 괄호를 붙여야 하는지 말아야 하는지를 고민할 필요가 없다.
데이터 멤버의 접근성에 대해 훨씬 정교한 제어를 할 수 있다. 데이터 멤버를 public으로 내놨다면 모두가 읽기 및 쓰기 접근권한을 갖게 되지만, 이 값을 읽고 쓰는 함수가 있다면 우리가 직접 권한을 구현할 수 있다.
class AccessLevels {
public:
int getReadOnly() const { return readOnly; }
int setReadWrite(int value) { readWrite = value; }
int getReadWrite() { return readWrite; }
void setWriteOnly(int value) { writeOnly = value; }
private:
int noAccess;
int readOnly;
int readWrite;
int writeOnly;
};
이렇게 세밀한 접근 제어는 나름대로의 중요성을 갖고 있다. 어떤 식으로든 외부에 노출시키면 안 되는 데이터 멤버들이 꽤 많기 때문이다.
다음은 캡슐화(encapsulation)이다. 함수를 통해서만 데이터 멤버에 접근할 수 있도록 구현해 두면, 데이터 멤버를 나중에 계산식으로 대체할 수도 있을 것이고, 사용자는 절대로 이 클래스를 넘보는 잘난 사람이 되지 못한다는 것이다.
예를 하나 들어보자. 자동차가 지나가는 속도를 모니터링하는 프로그램이다. 이 프로그램이 실행되면, 자동차가 지나갈 때마다 속도를 계산한 후에 지금까지 수집한 속도 데이터 집합에 그 속도를 추가한다.
class SpeedDataCollection {
public:
void addValue(int speed); // 새 데이터 값을 추가
double averageSoFar() const; // 평균 속도를 반환
};
averageSoFar을 구현하는 방법 중 한 가지는 지금까지 수집한 속도 데이터 전체의 평균값을 담는 어떤 데이터 멤버를 클래스 안에 넣어두는 방법이다. 이 방법은 SpeedDataCollection 객체 하나의 크기가 좀 커지지만 속도 측면에서는 좋다. 다른 방법은 averageSoFar이 호출될 때마다 평균값을 계산하는 방법이 있다. 이 방법은 속도는 느려지지만, 객체 하나의 크기는 첫 번째 방법보다 작다.
어쨌든 중요한 포인트는 "평균값 접근에 멤버 함수를 통하게 한다"(다른 말로 평균값을 캡슐화 한다)라는 점이다. 이렇게 함으로써 내부 구현을 이렇게 혹은 저렇게 바꿀 수 있게 되고, 사용자 쪽에서는 기껏 해 봤자 컴파일만 다시 하면 끝이다.
데이터 멤버를 함수 인터페이스 뒤에 감추게 되면 구현상의 융통성을 전부 누릴 수 있다. 예를 들면 데이터 멤버를 읽거나 쓸 때, 알림 메시지를 보낸다거나, 클래스의 불변속성 및 사전조건(precondition), 사후조건(postcondition)을 검증한다든지, 스레딩 환경에서 동기화를 건다든지 하는 일이다.
캡슐화는 우리가 느끼는 것보다 훨씬 중요하다. 사용자로부터 데이터 멤버를 숨기면(캡슐화하면), 클래스의 불변속성을 항상 유지하는데 절대로 소홀해질 수 없게 된다. 불변속성을 보여줄 수 있는 통로가 멤버 함수밖에 없기 때문이다. 그뿐 아니라 캡슐화는 현재의 구현을 나중에 바꾸기로 결정할 수 있는 권한을 예약하는 셈이다. public으로 되어있는 부분에 손을 대면 사용자 코드가 무너질 것이기 때문이다. 무릇 C++ 세상에서 public이란 '캡슐화 되지 않았다'는 뜻이며, 실질적인 측면에서 이야기할 때 '캡슐화되지 않았다'라는 말은 '바꿀 수 없다'라는 의미를 담고 있다.
protected도 앞서 말한 사정과 비슷하다. 문법적 일관성과 세밀한 접근 제어에 관한 이야기라면 public 데이터 멤버처럼 그대로 적용할 수 있다. 캡슐화도 마찬가지다.
어떤 것이 바뀌면 깨질 가능성을 가진 코드가 늘어날 때 캡슐화의 정도는 그에 반비례해서 작아진다. 어떤 클래스에 public 데이터 멤버가 있고, 이것을 제거한다고 가정하면 이것을 사용하는 사용자 코드는 전부 무사할 수 없을 것이다. protected 데이터 멤버를 제거한다고 가정하면 이것을 사용하는 파생 클래스는 무너질 것이다. 그 멤버에 대해 무엇을 바꾸기란 무척 힘들어진다는 뜻이다.
캡슐화 관점에서 쓸모 있는 접근 수준은 private(캡슐화 제공)와 private가 아닌 나머지(캡슐화 없음) 이렇게 둘 뿐이다.
이것만은 잊지 말자!
- 데이터 멤버는 private 멤버로 선언하자. 이를 통해 클래스 제작자는 문법적으로 일관성 있는 데이터 접근 통로를 제공할 수 있고, 필요에 따라서는 세밀한 접근 제어도 가능하며, 클래스의 불변속성을 강화할 수 있을 뿐 아니라, 내부 구현의 융통성도 발휘할 수 있다.
- protected는 public 보다 더 많이 '보호'받고 있는 것이 절대로 아니다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] 24: 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자 (1) | 2023.10.21 |
---|---|
[Effective C++] 23: 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자 (1) | 2023.10.20 |
[Effective C++] 21: 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자 (1) | 2023.10.18 |
[Effective C++] 20: '값에 의한 전달'보다는 '상수객체 참조자에 의한 전달' 방식을 택하는 편이 대개 낫다 (0) | 2023.10.17 |
[Effective C++] 19: 클래스 설계는 타입 설계와 똑같이 취급하자 (0) | 2023.10.16 |