C++ Classes and Objects: Constructors, Access Control,
이 글의 핵심
A class defines a blueprint; objects are the instances you create from it. This guide walks through C++ class fundamentals — constructors, encapsulation, destructors, and copy semantics — with complete working examples.
Class as Blueprint
A class defines the structure and behavior of a type. An object is a specific instance of that class — one particular entity created from the blueprint.
class = blueprint (defines once what the type is)
object = instance (you can create as many as you need)
The same class can produce thousands of objects, each with its own data, but all sharing the same behavior.
Your First Class
#include <iostream>
#include <string>
class Person {
public:
std::string name;
int age;
void introduce() const {
std::cout << "Hi, I'm " << name
<< " and I'm " << age << " years old.\n";
}
};
int main() {
Person alice; // create object on the stack
alice.name = "Alice";
alice.age = 25;
alice.introduce(); // Hi, I'm Alice and I'm 25 years old.
Person bob;
bob.name = "Bob";
bob.age = 30;
bob.introduce(); // Hi, I'm Bob and I'm 30 years old.
}
alice and bob are separate objects — each has its own name and age, but both share the introduce() method defined in the class.
Constructors
A constructor is a special function that runs automatically when an object is created. It has the same name as the class and no return type.
class Rectangle {
public:
double width;
double height;
// Default constructor — no arguments
Rectangle() : width(0), height(0) {}
// Parameterized constructor
Rectangle(double w, double h) : width(w), height(h) {}
double area() const {
return width * height;
}
double perimeter() const {
return 2 * (width + height);
}
};
int main() {
Rectangle r1; // uses default constructor: width=0, height=0
Rectangle r2(5.0, 3.0); // uses parameterized: width=5, height=3
std::cout << r2.area() << '\n'; // 15
std::cout << r2.perimeter() << '\n'; // 16
}
Member Initializer List
Prefer the initializer list (: member(value)) over assignment in the body. It’s more efficient and required for const members and references:
class Point {
const int x_; // const member — must use initializer list
const int y_;
public:
Point(int x, int y) : x_(x), y_(y) {} // correct
// Wrong — can't assign to const in body:
// Point(int x, int y) { x_ = x; y_ = y; }
};
Access Control: public and private
Encapsulation means hiding the internal state and only exposing a controlled interface. Use private for data members and public for methods:
class BankAccount {
private:
std::string owner_;
double balance_;
public:
BankAccount(const std::string& owner, double initial)
: owner_(owner), balance_(initial) {}
void deposit(double amount) {
if (amount > 0) {
balance_ += amount;
}
}
bool withdraw(double amount) {
if (amount > 0 && amount <= balance_) {
balance_ -= amount;
return true;
}
return false;
}
double getBalance() const { return balance_; }
const std::string& getOwner() const { return owner_; }
};
int main() {
BankAccount account("Alice", 1000.0);
account.deposit(500.0);
account.withdraw(200.0);
std::cout << account.getBalance() << '\n'; // 1300
// account.balance_ = -9999; // compile error — private!
}
Why encapsulation?
- The class controls what state is valid (no negative balance)
- You can change the internal representation later without breaking callers
- Users of the class only need to understand the public interface
const Member Functions
A const member function promises not to modify the object’s state. Mark getters as const:
class Circle {
double radius_;
public:
explicit Circle(double r) : radius_(r) {}
double radius() const { return radius_; } // const — just reads
double area() const { return 3.14159 * radius_ * radius_; } // const
void setRadius(double r) { // non-const — modifies
if (r >= 0) radius_ = r;
}
};
void printInfo(const Circle& c) { // takes const reference
std::cout << "Radius: " << c.radius() << '\n'; // OK — const method
std::cout << "Area: " << c.area() << '\n'; // OK — const method
// c.setRadius(5); // compile error — cannot call non-const on const
}
Destructors
A destructor runs automatically when an object goes out of scope (or is deleted). It’s the place to release resources:
class FileWriter {
FILE* file_;
public:
explicit FileWriter(const char* path) {
file_ = fopen(path, "w");
if (!file_) throw std::runtime_error("Cannot open file");
}
~FileWriter() { // destructor — called automatically
if (file_) {
fclose(file_); // release the resource
}
}
void write(const char* text) {
fputs(text, file_);
}
};
int main() {
{
FileWriter writer("output.txt");
writer.write("Hello\n");
} // writer goes out of scope — destructor runs, file closed automatically
}
This pattern — acquiring resources in the constructor and releasing them in the destructor — is called RAII (Resource Acquisition Is Initialization). It is the fundamental C++ idiom for preventing resource leaks.
Shallow Copy vs Deep Copy
When a class owns a raw pointer, the default copy behavior (shallow copy) copies the pointer value — two objects end up pointing to the same memory:
class Buffer {
int* data_;
int size_;
public:
Buffer(int size) : size_(size), data_(new int[size]) {}
~Buffer() { delete[] data_; }
// Default copy — copies the pointer, NOT the data
};
int main() {
Buffer b1(10);
Buffer b2 = b1; // both b1.data_ and b2.data_ point to the SAME array
} // b1 and b2 both try to delete[] the same array — double-free crash!
Fix option 1: Rule of Three — define copy constructor and copy assignment:
class Buffer {
int* data_;
int size_;
public:
Buffer(int size) : size_(size), data_(new int[size]) {}
// Copy constructor — deep copy
Buffer(const Buffer& other) : size_(other.size_), data_(new int[other.size_]) {
std::copy(other.data_, other.data_ + size_, data_);
}
// Copy assignment
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = new int[size_];
std::copy(other.data_, other.data_ + size_, data_);
}
return *this;
}
~Buffer() { delete[] data_; }
};
Fix option 2: Rule of Zero — use standard containers (preferred):
class Buffer {
std::vector<int> data_; // vector handles copy/move/destroy automatically
public:
explicit Buffer(int size) : data_(size) {}
// No destructor, copy constructor, or assignment operator needed
};
Prefer Rule of Zero: let std::vector, std::string, and std::unique_ptr handle resource management. Only write Rule of Three/Five when you genuinely need to manage a raw resource.
struct vs class
In C++, struct and class are nearly identical — the only difference is default access:
struct Point { // default: public
int x, y;
Point(int x, int y) : x(x), y(y) {}
};
class PrivatePoint { // default: private
int x, y;
public:
PrivatePoint(int x, int y) : x(x), y(y) {}
int getX() const { return x; }
};
Common convention:
structfor plain data aggregates (no invariants, all public)classfor objects with behavior and private state that needs protection
Complete Example: Student Grade Tracker
#include <iostream>
#include <string>
#include <vector>
#include <numeric>
class Student {
std::string name_;
std::vector<double> grades_;
public:
explicit Student(const std::string& name) : name_(name) {}
void addGrade(double grade) {
if (grade >= 0 && grade <= 100) {
grades_.push_back(grade);
}
}
double average() const {
if (grades_.empty()) return 0.0;
double sum = std::accumulate(grades_.begin(), grades_.end(), 0.0);
return sum / grades_.size();
}
char letterGrade() const {
double avg = average();
if (avg >= 90) return 'A';
if (avg >= 80) return 'B';
if (avg >= 70) return 'C';
if (avg >= 60) return 'D';
return 'F';
}
void printReport() const {
std::cout << name_ << ": "
<< average() << "% ("
<< letterGrade() << ")\n";
}
const std::string& name() const { return name_; }
};
int main() {
Student alice("Alice");
alice.addGrade(92);
alice.addGrade(88);
alice.addGrade(95);
alice.printReport(); // Alice: 91.667% (A)
Student bob("Bob");
bob.addGrade(72);
bob.addGrade(68);
bob.addGrade(75);
bob.printReport(); // Bob: 71.667% (C)
}
Key Takeaways
- A class is a blueprint; objects are instances created from it
- Constructors initialize objects; use member initializer lists for efficiency
privatehides state;publicexposes the interface — this is encapsulationconstmember functions promise not to modify the object — mark all getters asconst- Destructors run automatically when objects go out of scope — use RAII to release resources
- Shallow copy of a raw pointer causes double-free — use smart pointers or implement the Rule of Three/Five
- Prefer Rule of Zero: let
std::vector,std::string, and smart pointers manage resources automatically
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Learn C++ OOP from scratch: classes, constructors, public/private access, destructors, const member functions, and the R… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 조건문 | if/else/switch ‘완벽 정리’ [실수 방지 팁]
- [C++ Functions: Parameters, Return Values, Overloading, and](/en/blog/cpp-function-basics/
- C++ set/unordered_set | ‘중복 제거’ 완벽 가이드
이 글에서 다루는 키워드 (관련 검색어)
C++, class, object, OOP, beginner 등으로 검색하시면 이 글이 도움이 됩니다.