[Effective C++] 항목22 : 데이터 멤버가 선언될 곳은 private 영역임을 명심하자

Jangmanbo·2023년 4월 23일
0

Effective C++

목록 보기
22/33

데이터 멤버가 public이 아니어야 하는 이유

1. 문법적 일관성

데이터 멤버가 모두 public이 아니라면, 사용자가 어떤 객체에 접근할 수 있는 유일한 수단은 멤버 함수이다. 다시 말해서 클래스의 공개 인터페이스에 있는 것이 함수뿐이라면, 그 클래스의 멤버에 접근하고 싶을 때는 무조건 함수를 쓴다는 것이다.


2. 데이터 멤버의 접근성에 대한 정교한 제어

class AccessLevels {
public:
	...
    int getReadOnly() const { return readOnly; }
    void setReadWrite(int value) { readWrite = value; }
    int getReadWrite() const { return readWrtie; }
    void setWriteOnly(int value) { writeOnly = value; }
    
private:
	int noAccess;	// 이 int에는 접근 불가
    int readOnly;	// 이 int에는 읽기 전용 접근
    int readWrite;	// 이 int에는 읽기 쓰기 접근
    int WriteOnly;	// 이 int에는 쓰기 전용 접근
}

함수를 이용한다면 이렇게 세밀하게 데이터 멤버의 접근을 제어할 수 있다. 사실 모든 데이터 멤버에 읽기와 쓰기 함수를 달아 줄 일은 극히 드물기 때문에 이러한 접근 제어는 매우 중요한 포인트이다.


3. 캡슐화

이렇게 함수를 통해서만 데이터 멤버에 접근하도록 구현하면, 데이터 멤버를 계산식으로 대체할 수도 있으며 사용자는 이 클래스 객체에 대해 함부로 접근할 수 없다.

예시

다음은 자동차가 지나가는 속도를 모니터링하는 프로그램에 사용하는 클래스이다.
자동차가 지나갈 때마다 속도를 측정하여, 지금까지 측정한 속도의 평균값을 반환한다.

class SpeedDataCollection {
	...
public:
	void addValue(int speed);		// 새 데이터 멤버 추가
    double averageSoFar() const;	// 평균 속도 반환
}

averageSoFar의 구현 방법

  1. 현재의 평균값 유지하기
    평균값을 유지하기 위한 공간 할당이 필요하다. 아마도 현재의 평균값, 누적 총합, 데이터 개수 등이 SpeedDataCollection의 데이터 멤버로 필요할 것이다. 이렇게 구현하면 averageSoFar는 이미 계산되어 있는 평균값을 반환하기만 하면 되므로 효율적이다. (+ inline 함수라면 더욱 효율적)

  2. 호출될 때마다 평균값 계산하기
    averageSoFar의 속도는 느리지만, SpeedDataCollection 객체 하나의 크기는 첫 번째 방법보다 작을 것이다.

어떤 방법이 좋을 지는 상황에 따라 다르다.

평균값을 빈번하게 사용하고, 속도가 중요하며, 메모리 크기에 많이 구애받지 않는다면 첫 번째 방법이 좋다. 쓸 수 있는 메모리가 한정되어 있거나(ex. 임베디드 기계) 평균값이 자주 필요하지 않은 프로그램이라면 두 번째 방법이 좋다.

3-1. 함수 내부 구현의 다양성

중요한 것은 평균값 접근에 멤버 함수를 통하게 한다는 것, 즉 평균값을 캡슐화한다는 것이다. 멤버 변수(평균값)를 캡슐화함으로써 averageSoFar의 내부 구현을 바꿀 수 있게 된다.
따라서 널리 쓰이는 클래스일수록 내부 구현을 더 좋은 쪽으로 개선할 가능성을 남겨두기 위해 멤버 변수의 캡슐화가 필요하다.

3-2. 구현상의 융통성

구현상의 융통성을 얻을 수 있다. 예를 들면 데이터 멤버를 읽거나 쓸 때 다를 객체에 알림 메시지 보내기, 클래스의 불변속성 및 사전조건(precondition)/사후조건(postcondition) 검증, 스레딩 환경 동기화 등이 간편해진다.

3-3. 클래스의 불변속성 유지

사용자로부터 데이터 멤버를 캡슐화하면, 불변속성을 보여줄 방법이 멤버 함수 뿐이므로 불변속성을 유지하는 데에 용이하다.

참고: 불변속성이란?


protected도 마찬가지

protected는 public보다는 외부로부터 많이 가려져 있지만, 결국에는 public과 동일한 문제를 갖고 있다.

어떤 것이 바뀌면 깨질 가능성을 가진 코드가 늘어날 때 캡슐화의 정도는 그에 반비례해서 작아진다(항목 23). 즉 데이터 멤버가 클래스에서 제거될 때 깨질 수 있는 코드의 양에 반비례해서 그 데이터의 캡슐화 정도가 감소한다.

예시

어떤 public 데이터 멤버를 제거한다고 가정하자. 이 멤버와 관련된 얼마나 많은 코드가 망가질지 파악하기조차 어렵다. 이는 protected 멤버를 제거한 경우도 마찬가지이다. 해당 멤버를 사용하는 파생 클래스까지 영향이 미칠 것이기 때문이다.

이 말은 결국 어떤 데이터 멤버를 public이나 protected로 선언한 순간부터 해당 데이터 멤버에 대해 무언가를 바꾸기(제거하기) 어렵다는 말이다.

캡슐화의 관점에서 접근수준을 나눈다면 private과 private이 아닌 나머지 이렇게 둘 뿐이다.

정리

  • 데이터 멤버는 private 멤버로 선언해야 한다.
    • 문법적으로 일관성 있는 데이터 접근
    • 세밀한 접근 제어
    • 클래스의 불변속성 강화
    • 내부 구현의 융통성
  • protected는 public보다 더 많이 보호받고 있는 것이 절대로 아니다.

0개의 댓글