C++ class, 생성자로 불변식을 잡는 법
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.

Constructors and invariants
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.

public vs private (interface vs implementation)
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.