요약
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은 클래스 내부에서만 접근하는 구현 세부 사항을 분리합니다.
이 클래스에서 접근 제어자는 다음과 같이 역할을 나눕니다.
| 접근 제어자 | 의미 | 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 기준으로 정리한 일반 설명이며, 표준·구현·개정판에 따라 세부 서술은 달라질 수 있습니다.
Summary
A C++ class separates the public interface from the private implementation, and enforces invariants immediately upon object creation using a constructor. This article is a study note on Bjarne Stroustrup's A Tour of C++ 3rd ed. §2.3 Classes as of 2026-09-14; details may vary depending on the standard version, implementation, or edition. This is part 2/2 of the Chapter 2 (User-Defined Types) series.
What was missing from the struct Vector in section 2.2?
Short answer: A plain struct exposes internal representation directly, leaving the door open for uninitialized pointers and external tampering with internal state.
Part 1 of this series (covering sections 2.1 Introduction and 2.2 Structures) introduced struct Vector in the following form.
struct Vector {
double* elem;
int sz;
};
This is a plain data bundle. The user knows the internal representation — elem and sz — directly, and must call a separate initialization function to put the object into a valid state.
void vector_init(Vector& v, int s) {
v.elem = new double[s];
v.sz = s;
}
Two serious problems follow from this design. First, if a caller forgets to invoke vector_init, elem is left as an uninitialized pointer. Dereferencing it causes undefined behavior. Second, because elem and sz are fully public, external code can write v.sz = 1000; at any point. When the stored size disagrees with the actual allocated buffer, subsequent element accesses corrupt memory.
The goal of section 2.3 is to eliminate both problems: separate the interface from the implementation, and use a constructor to guarantee that every object starts in a valid state the moment it is created.
What is a constructor, and how does it differ from vector_init?
Short answer: A constructor is a special member function that the compiler calls automatically when an object is created, making a separate call like vector_init unnecessary and impossible to forget.
A constructor is a member function with the same name as its class. The compiler invokes it automatically whenever an object of that class is declared or dynamically allocated. Unlike vector_init, which the programmer had to remember to call, a constructor runs unconditionally at object creation — there is no way to skip it.
Below is the complete Vector class as presented in §2.3.
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;
};
The constructor Vector(int s) uses a member initializer list — the colon-separated list between the parameter list and the opening brace — to initialize elem and sz before the constructor body executes. elem{new double[s]} dynamically allocates an array of s doubles and stores its starting address; sz{s} stores the count. At the call site, declaring an object is all that is needed.
Vector v(6); // size-6 Vector — constructor called automatically
There is no separate initialization call, and therefore no initialization can be forgotten. Resource cleanup (the destructor and delete[]) is not covered in this section; the book addresses it in a later chapter.
What do public and private separate?
Short answer: public marks the interface that external code may use freely; private marks implementation details that only the class itself may access.
The access specifiers in the Vector class divide its members into two groups.
| Access specifier | Meaning | Vector example |
|---|---|---|
| public | The interface available to any external code | Vector(int s), operator[], size() |
| private | Implementation details accessible only to the class's own member functions | double* elem, int sz |
Keeping elem and sz private serves two purposes. First, it prevents external code from writing v.sz = 1000; and breaking the invariant that sz equals the allocated buffer length, which would lead to memory corruption on the next subscript operation. Second, if the internal representation changes in a future revision — say, from a raw pointer to a different storage strategy — code that uses only v[i] and v.size() needs no modification. This is the core benefit of encapsulation.
Why does operator[] return a reference?
Short answer: Returning double& allows the result to act as an lvalue, so assignments like v[i] = 7.5; write directly into the element rather than into a temporary copy.
Defining operator[] lets users write v[i] with the same syntax used for built-in arrays. The return type double& is critical: a reference is an lvalue, which means it can appear on the left-hand side of an assignment. If the return type were double (a value copy), v[i] = 7.5; would assign to the temporary copy and leave the actual array element unchanged.
The read_and_sum function from the notes demonstrates the operator in practice.
double read_and_sum(int s) {
Vector v(s); // create a size-s Vector; constructor runs automatically
for (int i = 0; i != v.size(); ++i) {
std::cin >> v[i]; // operator[] returns a reference; cin writes into elem[i]
}
double sum = 0;
for (int i = 0; i != v.size(); ++i) {
sum += v[i]; // read back through the same reference mechanism
}
return sum;
}
In the first loop, v[i] returns a reference to elem[i], so std::cin >> v[i] writes directly into the underlying array. In the second loop, the same reference mechanism lets each stored value be read back for accumulation.
What is the difference between struct and class?
Short answer: The two keywords are functionally equivalent, but struct defaults to public access while class defaults to private access.
In C++, struct and class are nearly identical. Constructors, member functions, access specifiers, and inheritance all work the same way with both keywords. The only meaningful distinction is the default access specifier.
struct: Members arepublicby default. Without an explicit access specifier, everything is visible to external code.class: Members areprivateby default. Without an explicit access specifier, nothing is accessible from outside.
The design guideline from the notes: use struct for plain data bundles (POD) where all members are meant to be public, and use class when the type enforces invariants and hides its implementation.
Seen across the series: the struct Vector from section 2.2 had fully public data — the default matched its role as a plain data bundle. The class Vector of section 2.3 exposes only the interface (operator[], size()) and hides the representation (elem, sz). This structural shift is precisely why class is the right keyword once invariant enforcement and encapsulation become goals.
FAQ
Short answer: Frequently asked questions about §2.3 Classes.
| Question | Answer |
|---|---|
What is the danger of using only vector_init instead of a constructor? |
If the initialization call is accidentally omitted, elem remains an uninitialized pointer. Dereferencing it through v[i] is undefined behavior and can corrupt memory or crash the program. A constructor eliminates this risk because the compiler always calls it at object creation — the programmer cannot forget it. |
What goes wrong if elem and sz are public? |
External code can write v.sz = 1000; without touching the underlying buffer, which was only allocated for a smaller count. The next subscript access beyond the real buffer boundary reads or writes unowned memory, causing memory corruption. Keeping both members private makes this category of error impossible from outside the class. |
How should I decide between struct and class? |
Use struct when the type is a simple data bundle where all members are naturally public. Use class when the type enforces an invariant and needs to hide implementation details. Remembering the default access — public for struct, private for class — makes the guideline concrete. |
Does the Vector in this section cover the destructor or delete[]? |
No. Section 2.3 focuses on constructors and access control. Resource release — the destructor and delete[] — appears in a later chapter of the book. |
References
Short answer: Facts in this article are drawn from Bjarne Stroustrup's book and the cppreference official documentation, checked as of 2026-09-14.
- Bjarne Stroustrup, A Tour of C++ (3rd ed.), §2.3 Classes — primary study source for this article.
- Class declaration — cppreference.com — Used to verify class declaration syntax and the default access specifier difference between
structandclass. - Constructors and member initializer lists — cppreference.com — Used to verify constructor definition rules and member initializer list syntax.
- Access specifiers — cppreference.com — Used to verify the rules governing
publicandprivateaccess. - Operator overloading — cppreference.com — Referenced for
operator[]definition conventions.
This article is a general study note based on Bjarne Stroustrup's A Tour of C++ 3rd ed. §2.3 Classes as of 2026-09-14. Details may vary depending on the C++ standard version, compiler implementation, or edition of the book.
'C++' 카테고리의 다른 글
| C++20 jthread stop_token 사용법, 스레드 취소를 어떻게 하나 (0) | 2026.09.22 |
|---|---|
| C++ 1.7 포인터와 레퍼런스 정리: 주소, 배열, 참조, nullptr까지 (0) | 2026.09.19 |
| A Tour of C++ 1.9: 하드웨어 매핑과 포인터, 참조의 차이 (0) | 2026.09.18 |
| C++ 코딩 습관, Tour 1.10 Advice 정리 (0) | 2026.09.17 |
| C++ std::atomic, 데이터 레이스를 막는 법 (0) | 2026.09.17 |
