누가 여러분의 데이터에 접근할 수 있는가
첫 클래스를 작성했을 때 여러분은 아마 모든 멤버를 바깥세상에 노출했을 것입니다. 그래도 동작은 하지만, 클래스가 존재하는 주된 이유 중 하나를 내버리는 셈입니다. 바로 캡슐화 — 클래스의 내부 상태를 숨겨서 프로그램의 나머지 부분이 통제된 표면을 통해서만 그것과 상호작용하도록 하는 것입니다. 접근 지정자는 그 경계를 긋는 방법입니다.
정확히 셋이 있습니다. public, private, protected입니다. 각각은 자기 뒤에 오는 멤버에 라벨을 붙이고, 그 라벨이 어떤 코드가 그것들을 읽거나 쓸 수 있는지를 결정합니다. 이를 제대로 하면 클래스가 스스로 규칙을 강제하고, 잘못하면 어디에서든 발생한 버그 하나가 객체의 상태를 망가뜨릴 수 있습니다.
세 가지 지정자
지정자는 키워드 뒤에 콜론이 오는 형태입니다. 그 뒤에 선언된 모든 멤버는 다음 지정자가 나올 때까지 그 접근 수준에 속합니다.
마지막 줄의 주석을 해제하면 컴파일러가 빌드를 거부합니다. balance는 private이므로 main은 그것을 직접 건드릴 수 없습니다. 바로 이것이 핵심입니다. 잔액을 바꾸는 유일한 방법은 deposit을 통하는 것이고, 따라서 나중에 검증(음수 입금 금지, 로깅, 한도)을 한 곳에 추가하고 그것이 항상 적용된다고 신뢰할 수 있습니다.
전체 정리는 다음과 같습니다.
// 어디에서 접근 가능한가...
// public 어디에서나 (객체를 가진 모든 코드)
// private 같은 클래스 자신의 멤버만 (+ friend)
// protected 같은 클래스의 멤버 그리고 파생 클래스 (+ friend)
class와 struct: 기본값
지정자 블록은 원하는 만큼, 원하는 순서로 쓸 수 있습니다. 첫 지정자를 쓰기 전의 멤버가 무엇이 되는지는 class를 썼는지 struct를 썼는지에 달려 있습니다.
- **
class**에서 멤버는 기본적으로private입니다. - **
struct**에서 멤버는 기본적으로public입니다.
이 기본값이 두 키워드 사이의 유일한 언어 수준 차이입니다. struct도 class와 똑같이 메서드, 생성자, private 구역을 가질 수 있습니다.
관례는 단순한 공개 데이터 묶음에는 struct를, 동작과 숨겨진 상태를 원할 때는 class를 쓰는 것입니다. 하지만 컴파일러는 이를 강제하지 않으며, 다른 것은 기본값뿐입니다.
게터와 세터를 이용한 캡슐화
일상적인 패턴은 이렇습니다. 데이터는 private 구역에 두고, public 메서드가 통제된 접근을 제공합니다. 읽기 전용 게터는 값을 반환하고, 세터는 대입하기 전에 검증합니다. 바로 여기서 private가 보답합니다.
celsius가 private이기 때문에 유효하지 않은 값을 몰래 집어넣을 방법이 없습니다. 모든 쓰기는 불변식을 지키는 setCelsius를 거쳐야 합니다. 게터가 const로 표시된 점에 주목하세요. 이들은 객체를 수정하지 않겠다고 약속하므로 const Temperature 객체에서도 호출할 수 있습니다.
protected와 상속
protected는 상속이 등장할 때만 의미가 있습니다. 외부 코드에는 private처럼 동작하지만, 파생 클래스는 그것에 접근할 수 있습니다. 서브클래스가 정당하게 필요로 하지만 외부에는 여전히 노출되어서는 안 되는 멤버에 사용하세요.
초보자가 흔히 저지르는 실수는 "혹시 서브클래스가 필요할지도 모르니까"라며 모든 데이터 멤버에 protected를 쓰는 것입니다. 이는 클래스의 계약을 조용히 넓힙니다. 이제 모든 서브클래스가 그 필드에 의존할 수 있어 자유롭게 바꿀 수 없게 됩니다. private를 우선하고, 파생 클래스가 정말로 접근이 필요할 때만 protected로 승격하세요.
friend라는 탈출구
때로는 외부의 함수나 클래스 하나가 정당하게 여러분의 내부를 들여다봐야 합니다. 고전적인 예는 멤버로 만들 수 없는 << 같은 연산자입니다. friend 키워드는 그 지명된 하나의 대상에게 여러분의 private 및 protected 멤버에 대한 접근 권한을 부여하고, 그 외에는 아무것도 주지 않습니다.
friend는 의도적이고 외과적인 예외입니다. 클래스 자신이 누구를 신뢰하는지 정확히 지명하므로 누구도 바깥에서 스스로에게 접근 권한을 부여할 수 없습니다. 아껴 쓰세요. friend를 많이 추가하고 있다면, 애초에 멤버가 private이어서는 안 됐거나 설계를 다시 생각해야 한다는 신호일 것입니다.
피해야 할 흔한 실수
- 모든 멤버를
public으로 만들기. 쉬워 보이지만 캡슐화가 주는 모든 검증과 불변식을 잃게 됩니다. 기본은private데이터에public메서드입니다. class의 기본값을 잊기.class Foo { int x; };는x를 private로 만들므로foo.x = 5는 컴파일되지 않습니다. 단순한 데이터 묶음을 의도했다면struct를 쓰거나public:라벨을 추가하세요.protected남용하기. 이는private보다 약한 경계이며 상속에서만 의미가 있습니다. 곳곳에 쓰면 바꾸고 싶을지도 모르는 필드에 서브클래스가 결합됩니다.private를 보안 기능으로 기대하기. 이는 우발적 접근을 막는 컴파일 시점 규칙이지 암호화가 아닙니다. 바이트는 여전히 메모리에 있습니다.private는 깔끔한 설계를 위한 것이지 비밀 유지를 위한 것이 아닙니다.
다음: 구조체
이제 struct가 사실은 멤버가 기본적으로 public일 뿐인 class라는 것을 보았습니다. 다음 페이지 구조체에서는 이 "기본 public" 기본값이 정확히 원하는 것이 되는 경우 — 관련된 값들을 묶는 가벼운 집합체 — 와 struct가 완전한 기능의 클래스와 나란히 관용적인 C++에서 어떻게 쓰이는지를 깊이 다룹니다.
자주 묻는 질문
C++에서 public, private, protected의 차이는 무엇인가요?
public 멤버는 어디에서나 접근할 수 있습니다. private 멤버는 같은 클래스 내부(그리고 그 friend)에서만 접근할 수 있습니다. protected는 private와 비슷하지만, 추가로 파생 클래스가 그 멤버에 접근할 수 있게 해 줍니다. 관례적으로 데이터는 private로 유지하고 동작은 public 메서드를 통해 노출합니다.
C++에서 멤버는 기본적으로 private인가요?
class에서는 그렇습니다. 접근 지정자를 쓰기 전까지는 모든 것이 private입니다. struct에서는 기본값이 public입니다. 이 하나의 기본값이 C++에서 class와 struct의 유일한 실질적 차이입니다. 둘 다 메서드, 생성자, 접근 지정자를 가질 수 있습니다.
C++에서 friend 키워드는 무엇을 하나요?
friend는 특정 함수나 클래스 하나에 여러분의 private 및 protected 멤버에 대한 접근 권한을 부여합니다. 이는 캡슐화에 대한 의도적이고 좁은 예외입니다. 클래스가 누구를 신뢰하는지 정확히 지명하므로 접근이 암묵적으로 부여되는 일은 결코 없습니다.