C++ class, 생성자로 불변식을 잡는 법
C++ class는 공개 인터페이스와 비공개 구현을 나누고, 생성자로 객체가 만들어지는 즉시 불변식을 잡게 합니다.
이 글은 Bjarne Stroustrup, A Tour of C++ 3rd ed. §2.3 Classes 공부 노트를 2026-09-14 기준으로 정리한 일반 설명이며, 표준·구현·개정판에 따라 세부 서술은 달라질 수 있습니다. A Tour of C++ Ch.2(User-Defined Types) 시리즈 2/2입니다.
2.2 struct Vector는 무엇이 부족했나?
한 줄 답: 단순 데이터 묶음인 struct는 초기화를 누락하거나 외부에서 내부 상태를 임의로 변경할 위험이 있습니다.
이 시리즈의 1편(2.1 Introduction + 2.2 Structures)에서 다룬 struct Vector는 다음과 같은 형태였습니다.
struct Vector {
double* elem;
int sz;
};
이 구조체는 단순한 데이터 묶음입니다. 외부에서 elem과 sz라는 내부 표현을 직접 알고 조작해야 하므로, 사용자는 별도의 초기화 함수를 따로 호출해야만 했습니다.
void vector_init(Vector& v, int s) {
v.elem = new double[s];
v.sz = s;
}
이 방식에는 두 가지 치명적인 문제가 있습니다. 첫째, 사용자가 vector_init 호출을 실수로 누락하면 elem이 초기화되지 않은 포인터를 가리키게 됩니다. 이후 해당 포인터를 참조하면 미정의 동작(undefined behavior)이 발생합니다. 둘째, elem과 sz가 완전히 공개(public)되어 있어 외부에서 v.sz를 임의로 변경할 수 있습니다. 실제 할당된 배열 크기와 sz 값이 불일치(inconsistency)하면 메모리 오염으로 이어질 수 있습니다.
2.3절의 목표는 이러한 문제를 해결하기 위해 인터페이스와 구현을 엄격히 분리하고, 생성자를 통해 객체가 생성되는 즉시 올바른 상태를 강제하는 것입니다.
생성자는 무엇이고 vector_init과 어떻게 다르나?
한 줄 답: 생성자는 객체 생성 시 컴파일러가 자동으로 호출하는 특수 멤버 함수로, vector_init처럼 별도로 호출할 필요 없이 초기화를 강제합니다.

생성자와 불변조건
생성자(constructor)는 클래스 이름과 동일한 이름을 갖는 특수 멤버 함수입니다. 객체가 선언되거나 생성되는 시점에 컴파일러가 자동으로 호출합니다. 별도의 초기화 함수를 호출해야 했던 vector_init과 달리, 생성자는 호출 누락이 원천적으로 불가능합니다.
아래는 §2.3에서 소개하는 Vector의 전체 클래스 정의입니다.
class Vector {
public:
Vector(int s) : elem{new double[s]}, sz{s} {
}
double& operator[](int i) {
return elem[i];
}
int size() {
return sz;
}
private:
double* elem;
int sz;
};
생성자 Vector(int s)는 콜론(:) 뒤에 오는 **멤버 초기화 리스트(member initializer list)**를 통해 elem과 sz를 초기화합니다. elem{new double[s]}는 크기 s인 double 배열을 동적 할당하고 그 시작 주소를 elem에 저장합니다. sz{s}는 sz를 s로 초기화합니다.
사용 측에서는 다음과 같이 객체를 선언하는 것만으로 생성자가 자동 호출됩니다.
Vector v(6); // 크기 6의 Vector — 생성자 자동 호출
vector_init(v, 6)처럼 별도로 호출할 필요가 없으므로, 초기화 누락으로 인한 미정의 동작 위험이 사라집니다. 자원 해제(소멸자·delete[])는 이 절에서 다루지 않으며, 책의 후속 단원에서 등장합니다.
public과 private은 무엇을 나누나?
한 줄 답: public은 외부에서 사용하는 공개 인터페이스를, private은 클래스 내부에서만 접근하는 구현 세부 사항을 분리합니다.

public vs private (인터페이스 vs 구현)
이 클래스에서 접근 제어자는 다음과 같이 역할을 나눕니다.
| 접근 제어자 | 의미 | Vector 적용 예시 |
|---|---|---|
| public | 외부 코드가 자유롭게 사용할 수 있는 공개 인터페이스 | Vector(int s), operator[], size() |
| private | 클래스 내부 멤버 함수만 접근 가능한 구현 세부 사항 | double* elem, int sz |
elem과 sz를 private으로 숨긴 이유는 두 가지입니다. 첫째, 외부에서 v.sz를 임의로 변경하여 실제 버퍼 크기와 불일치가 생기는 메모리 오염을 방지하기 위해서입니다. 둘째, 내부 표현(예: 포인터 기반에서 다른 자료구조로)이 나중에 바뀌어도 v[i]나 v.size()처럼 인터페이스를 사용하는 코드는 수정할 필요가 없습니다. 이것이 캡슐화(encapsulation)의 핵심 이점입니다.
operator[]는 왜 참조를 반환하나?
한 줄 답: 참조(double&)를 반환해야 v[i] = 7.5;처럼 값을 대입(lvalue)할 수 있기 때문입니다.
operator[]를 정의하면 내장 배열과 동일한 v[i] 문법으로 원소에 접근할 수 있습니다. 반환 타입이 double&인 이유는 반환값이 lvalue(좌변값)로 동작하여 대입 연산의 대상이 될 수 있기 때문입니다. 만약 double(값 복사)을 반환했다면 v[i] = 7.5;는 복사본에 대입하는 것이 되어 원소 값이 변경되지 않습니다.
아래는 노트의 read_and_sum 예시입니다. operator[]가 실제 사용 상황에서 어떻게 동작하는지 보여줍니다.
double read_and_sum(int s) {
Vector v(s); // 크기 s의 Vector 생성 (생성자 자동 호출)
for (int i = 0; i != v.size(); ++i) {
std::cin >> v[i]; // operator[]가 참조를 반환하므로 대입 가능
}
double sum = 0;
for (int i = 0; i != v.size(); ++i) {
sum += v[i]; // operator[]로 원소 값 읽기
}
return sum;
}
첫 번째 루프에서 v[i]는 elem[i]에 대한 참조를 반환하므로 std::cin >> v[i]가 실제 배열 원소에 직접 값을 씁니다. 두 번째 루프에서도 동일한 참조를 통해 저장된 값을 읽습니다.
struct와 class는 무엇이 다르나?
한 줄 답: 기능은 대등하지만 struct는 기본 접근 지정자가 public이고, class는 기본 접근 지정자가 private입니다.
C++에서 struct와 class는 기능적으로 거의 동일합니다. 생성자, 멤버 함수, 접근 제어자, 상속 모두 두 키워드에서 동일하게 사용할 수 있습니다. 유일한 실질적 차이는 **기본 접근 지정자(default access specifier)**입니다.
struct: 기본 접근이public입니다. 별도의 접근 제어자를 명시하지 않으면 모든 멤버가 외부에 공개됩니다.class: 기본 접근이private입니다. 별도의 접근 제어자를 명시하지 않으면 모든 멤버가 외부에서 접근 불가합니다.
설계 규칙(노트 기준): 불변식을 강제하고 구현 세부 사항을 숨겨야 한다면 class를 사용합니다. 단순히 데이터 조각들을 묶는 순수 데이터 구조(POD)라면 struct를 사용합니다.
시리즈 관점에서 보면, 1편의 struct Vector는 elem과 sz가 기본으로 공개된 데이터 묶음이었습니다. 2.3절의 class Vector는 인터페이스(operator[], size())는 공개하되 내부 표현(elem, sz)은 숨기는 구조로 바뀌었습니다. 이 전환이 바로 struct에서 class로 넘어가는 핵심 이유입니다.
FAQ
한 줄 답: §2.3 Classes와 관련하여 자주 묻는 질문을 정리합니다.
| 질문 | 답변 |
|---|---|
생성자를 쓰지 않고 vector_init만 쓰면 무엇이 위험합니까? | 실수로 호출을 누락하면 elem이 초기화되지 않은 포인터를 가리키게 됩니다. 이 상태에서 v[i]를 참조하면 미정의 동작(undefined behavior)이 발생합니다. 생성자는 객체 선언 시점에 컴파일러가 반드시 호출하므로 이 위험이 사라집니다. |
elem과 sz를 public으로 두면 어떤 문제가 생깁니까? | 외부에서 v.sz = 1000;처럼 임의로 변경하면 실제 할당된 배열 크기와 불일치가 발생합니다. 이후 v[i]로 배열 범위를 넘는 위치에 접근하면 메모리 오염으로 이어질 수 있습니다. |
struct와 class를 고르는 기준은 무엇입니까? | 단순히 데이터 조각을 묶는 목적이라면 struct를, 불변식을 보호하고 내부 구현을 숨겨야 한다면 class를 사용합니다. 기본 접근 지정자(struct는 public, class는 private)를 기억하면 선택 기준이 명확해집니다. |
이 절의 Vector는 소멸자나 delete[]를 다룹니까? | 아직 다루지 않습니다. §2.3은 생성자와 접근 제어에 집중하며, 자원 해제(소멸자·delete[])는 책의 후속 단원에서 등장합니다. |
출처
한 줄 답: 본문 사실은 2026-09-14 기준으로 확인한 Bjarne Stroustrup의 저서와 cppreference 공식 문서를 기반으로 합니다.
- Bjarne Stroustrup, A Tour of C++ (3rd ed.), §2.3 Classes — 이 글의 주요 학습 출처입니다.
- Class declaration — cppreference.com — class 선언 문법과 struct vs class 기본 접근 지정자를 확인했습니다.
- Constructors and member initializer lists — cppreference.com — 생성자 정의와 멤버 초기화 리스트 문법을 확인했습니다.
- Access specifiers — cppreference.com — public 및 private 접근 제어자의 규칙을 확인했습니다.
- Operator overloading — cppreference.com —
operator[]정의 규칙을 참조했습니다.
이 글은 Bjarne Stroustrup, A Tour of C++ 3rd ed. §2.3 Classes 공부 노트를 2026-09-14 기준으로 정리한 일반 설명이며, 표준·구현·개정판에 따라 세부 서술은 달라질 수 있습니다.