C++ OOP 핵심 — 클래스·상속·다형성·가상함수
개요 — C++ OOP 핵심 4가지
Section titled “개요 — C++ OOP 핵심 4가지”| 개념 | 역할 |
|---|---|
| 캡슐화(Encapsulation) | 데이터와 함수를 클래스로 묶고, 접근을 제한 |
| 상속(Inheritance) | 기존 클래스를 확장해 코드 재사용 |
| 다형성(Polymorphism) | 같은 인터페이스로 다른 동작 수행 |
| 추상화(Abstraction) | 구현을 숨기고 인터페이스만 노출 |
1. 클래스와 구조체
Section titled “1. 클래스와 구조체”1.1 class vs struct 차이
Section titled “1.1 class vs struct 차이”C++에서 class와 struct의 유일한 문법적 차이는 기본 접근 제어자입니다.
| 키워드 | 기본 접근 제어자 | 기본 상속 방식 |
|---|---|---|
class | private | private |
struct | public | public |
// struct: 데이터 묶음, 멤버 기본 publicstruct Point{ float X; float Y; float Z;};
// class: 동작을 포함하는 객체, 멤버 기본 privateclass Player{public: Player(const std::string& name, int health) : m_Name(name), m_Health(health) {}
const std::string& GetName() const { return m_Name; } int GetHealth() const { return m_Health; } void TakeDamage(int amount);
private: std::string m_Name; int m_Health;};1.2 접근 제어자
Section titled “1.2 접근 제어자”class BankAccount{public: // 외부에서 자유롭게 접근 가능 BankAccount(const std::string& owner, double balance) : m_Owner(owner), m_Balance(balance) {}
void Deposit(double amount) { if (amount > 0) { m_Balance += amount; LogTransaction("Deposit", amount); } }
bool Withdraw(double amount) { if (amount > 0 && m_Balance >= amount) { m_Balance -= amount; LogTransaction("Withdraw", amount); return true; } return false; }
double GetBalance() const { return m_Balance; }
protected: // 이 클래스와 파생 클래스에서만 접근 가능 void LogTransaction(const std::string& type, double amount) { std::cout << "[" << type << "] " << amount << " (balance: " << m_Balance << ")\n"; }
private: // 이 클래스 내부에서만 접근 가능 std::string m_Owner; double m_Balance;};1.3 생성자와 소멸자
Section titled “1.3 생성자와 소멸자”#include <iostream>#include <string>
class Resource{public: // 기본 생성자 Resource() : m_Id(0), m_Name("default") { std::cout << "Resource(): " << m_Name << "\n"; }
// 매개변수 생성자 explicit Resource(int id, const std::string& name) : m_Id(id), m_Name(name) { std::cout << "Resource(id, name): " << m_Name << "\n"; }
// 복사 생성자 Resource(const Resource& other) : m_Id(other.m_Id), m_Name(other.m_Name + "_copy") { std::cout << "Resource(copy): " << m_Name << "\n"; }
// 소멸자 ~Resource() { std::cout << "~Resource(): " << m_Name << "\n"; }
int GetId() const { return m_Id; } const std::string& GetName() const { return m_Name; }
private: int m_Id; std::string m_Name;};
int main(){ Resource r1(1, "Texture"); // 매개변수 생성자 Resource r2(r1); // 복사 생성자 Resource r3; // 기본 생성자
return 0; // ~Resource() 역순으로 3번 호출}2.1 상속과 접근 지정
Section titled “2.1 상속과 접근 지정”#include <string>#include <iostream>
class Animal{public: Animal(const std::string& name, int age) : m_Name(name), m_Age(age) {}
virtual ~Animal() = default;
void Breathe() const { std::cout << m_Name << " breathes.\n"; }
const std::string& GetName() const { return m_Name; } int GetAge() const { return m_Age; }
protected: std::string m_Name; int m_Age;};
// public 상속: Animal의 public → Dog의 public, protected → protectedclass Dog : public Animal{public: Dog(const std::string& name, int age, const std::string& breed) : Animal(name, age) // 부모 생성자 명시적 호출 , m_Breed(breed) { }
void Bark() const { // protected 멤버 접근 가능 std::cout << m_Name << " (" << m_Breed << ") barks!\n"; }
private: std::string m_Breed;};
int main(){ Dog dog("Buddy", 3, "Labrador"); dog.Breathe(); // Animal의 public 함수 상속 dog.Bark(); // Dog의 고유 함수 std::cout << "Age: " << dog.GetAge() << "\n";
return 0;}2.2 상속 접근 지정 비교
Section titled “2.2 상속 접근 지정 비교”public 상속 : 부모의 public → 자식 public, protected → protectedprotected 상속 : 부모의 public/protected → 자식 protectedprivate 상속 : 부모의 public/protected → 자식 private3. 가상함수와 다형성
Section titled “3. 가상함수와 다형성”3.1 가상함수 없이는 다형성이 없다
Section titled “3.1 가상함수 없이는 다형성이 없다”#include <iostream>
class Shape{public: // 가상함수 아님 → 정적 바인딩 void Draw() const { std::cout << "Shape::Draw()\n"; }};
class Circle : public Shape{public: void Draw() const // 오버라이드처럼 보이지만... { std::cout << "Circle::Draw()\n"; }};
int main(){ Circle c; Shape* ptr = &c; ptr->Draw(); // Shape::Draw() 출력 ← 기대한 Circle::Draw()가 아님! return 0;}3.2 virtual 키워드와 동적 바인딩
Section titled “3.2 virtual 키워드와 동적 바인딩”#include <iostream>#include <memory>#include <vector>
class Shape{public: // virtual 선언: 동적 바인딩 활성화 virtual void Draw() const { std::cout << "Shape::Draw()\n"; }
virtual float Area() const { return 0.f; }
// 기반 클래스 소멸자는 반드시 virtual virtual ~Shape() = default;};
class Circle : public Shape{public: explicit Circle(float radius) : m_Radius(radius) {}
// override 키워드: 가상함수 오버라이드임을 명시 (컴파일러 검증) void Draw() const override { std::cout << "Circle::Draw() r=" << m_Radius << "\n"; }
float Area() const override { return 3.14159f * m_Radius * m_Radius; }
private: float m_Radius;};
class Rectangle : public Shape{public: Rectangle(float w, float h) : m_Width(w), m_Height(h) {}
void Draw() const override { std::cout << "Rectangle::Draw() " << m_Width << "x" << m_Height << "\n"; }
float Area() const override { return m_Width * m_Height; }
private: float m_Width; float m_Height;};
int main(){ // 기반 클래스 포인터로 파생 클래스 객체를 다형성으로 사용 std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Circle>(5.f)); shapes.push_back(std::make_unique<Rectangle>(4.f, 3.f)); shapes.push_back(std::make_unique<Circle>(2.f));
for (const auto& shape : shapes) { shape->Draw(); // 동적 바인딩 std::cout << "Area: " << shape->Area() << "\n"; }
return 0;}3.3 vtable 동작 원리
Section titled “3.3 vtable 동작 원리”virtual 함수가 있는 클래스는 컴파일러가 **vtable(가상 함수 테이블)**을 생성합니다.
[Shape vtable] [Circle vtable]+------------------+ +--------------------+| Shape::Draw ptr | | Circle::Draw ptr || Shape::Area ptr | | Circle::Area ptr |+------------------+ +--------------------+
Circle 객체:+-------------------+| vptr ─────────────────► Circle vtable+-------------------+| m_Radius |+-------------------+
Shape* ptr = &circleObj;ptr->Draw(); → ptr의 vptr가 가리키는 테이블에서 Draw 찾음 → Circle::Draw 호출 (동적 바인딩)virtual함수 하나라도 있으면 객체에vptr(포인터 크기) 오버헤드 추가- 가상 함수 호출은 포인터 역참조 1회 추가 비용 (일반적으로 무시 가능)
4. 순수 가상함수와 추상 클래스
Section titled “4. 순수 가상함수와 추상 클래스”4.1 순수 가상함수 선언
Section titled “4.1 순수 가상함수 선언”#include <iostream>#include <string>
// 추상 클래스: 순수 가상함수를 하나 이상 포함class ISerializable{public: virtual ~ISerializable() = default;
// 순수 가상함수: = 0 으로 선언, 파생 클래스에서 반드시 구현 virtual std::string Serialize() const = 0; virtual void Deserialize(const std::string& data) = 0;
// 추상 클래스도 일반(비순수) 멤버 함수를 가질 수 있음 void SaveToFile(const std::string& path) const { std::string data = Serialize(); // 다형성 호출 // 파일 저장 로직... std::cout << "Saved to " << path << ": " << data << "\n"; }};
class PlayerData : public ISerializable{public: PlayerData(const std::string& name, int score) : m_Name(name), m_Score(score) {}
// 순수 가상함수 구현 (필수) std::string Serialize() const override { return m_Name + ":" + std::to_string(m_Score); }
void Deserialize(const std::string& data) override { size_t pos = data.find(':'); if (pos != std::string::npos) { m_Name = data.substr(0, pos); m_Score = std::stoi(data.substr(pos + 1)); } }
private: std::string m_Name; int m_Score;};
int main(){ // ISerializable obj; // 컴파일 에러: 추상 클래스는 인스턴스 생성 불가
PlayerData pd("Alice", 1500); pd.SaveToFile("save.dat"); // Saved to save.dat: Alice:1500
return 0;}4.2 인터페이스 패턴 (순수 가상함수만 포함)
Section titled “4.2 인터페이스 패턴 (순수 가상함수만 포함)”// C++에는 interface 키워드가 없음// 관례상 순수 가상함수만 포함한 추상 클래스를 인터페이스로 사용class IDamageable{public: virtual ~IDamageable() = default; virtual void TakeDamage(float amount) = 0; virtual bool IsAlive() const = 0;};
class IMovable{public: virtual ~IMovable() = default; virtual void MoveTo(float x, float y, float z) = 0; virtual void Stop() = 0;};
// 다중 상속으로 여러 인터페이스 구현class Enemy : public IDamageable, public IMovable{public: Enemy() : m_Health(100.f) {}
void TakeDamage(float amount) override { m_Health -= amount; if (m_Health < 0.f) m_Health = 0.f; std::cout << "Enemy HP: " << m_Health << "\n"; }
bool IsAlive() const override { return m_Health > 0.f; }
void MoveTo(float x, float y, float z) override { std::cout << "Enemy moves to (" << x << ", " << y << ", " << z << ")\n"; }
void Stop() override { std::cout << "Enemy stops\n"; }
private: float m_Health;};5. override와 final 키워드
Section titled “5. override와 final 키워드”5.1 override — 실수 방지
Section titled “5.1 override — 실수 방지”class Base{public: virtual void Update(float deltaTime) {} virtual ~Base() = default;};
class Derived : public Base{public: // override 없이: 오타가 있어도 경고 없이 새 함수로 처리됨 void update(float deltaTime) // 소문자 u → Base::Update를 오버라이드하지 못함 { }
// override 있음: 서명 불일치 시 컴파일 에러 // void update(float deltaTime) override // 컴파일 에러: Base에 해당 가상함수 없음
void Update(float deltaTime) override // OK { std::cout << "Derived::Update\n"; }};5.2 final — 상속/오버라이드 금지
Section titled “5.2 final — 상속/오버라이드 금지”// final 클래스: 더 이상 상속 불가class Singleton final{public: static Singleton& GetInstance() { static Singleton instance; return instance; }
private: Singleton() = default;};
// class ExtendedSingleton : public Singleton {}; // 컴파일 에러
class Animal{public: virtual void Speak() = 0; virtual ~Animal() = default;};
class Cat : public Animal{public: // final 함수: 이 클래스의 파생에서 재정의 불가 void Speak() override final { std::cout << "Meow\n"; }};
class SpecialCat : public Cat{public: // void Speak() override {} // 컴파일 에러: Cat::Speak는 final};6. 실전 패턴 — 컴포넌트 기반 캐릭터
Section titled “6. 실전 패턴 — 컴포넌트 기반 캐릭터”#include <iostream>#include <memory>#include <vector>#include <string>
// 컴포넌트 기반 클래스 (추상)class Component{public: virtual ~Component() = default; virtual void Update(float dt) = 0; virtual std::string GetName() const = 0;};
// 체력 컴포넌트class HealthComponent : public Component{public: explicit HealthComponent(float maxHp) : m_MaxHp(maxHp), m_Hp(maxHp) {}
void Update(float dt) override {} // 체력은 틱 업데이트 불필요
std::string GetName() const override { return "HealthComponent"; }
void TakeDamage(float amount) { m_Hp = std::max(0.f, m_Hp - amount); std::cout << "HP: " << m_Hp << "/" << m_MaxHp << "\n"; }
bool IsAlive() const { return m_Hp > 0.f; }
private: float m_MaxHp; float m_Hp;};
// 이동 컴포넌트class MovementComponent : public Component{public: explicit MovementComponent(float speed) : m_Speed(speed) {}
void Update(float dt) override { // 이동 처리 (예시) }
std::string GetName() const override { return "MovementComponent"; }
float GetSpeed() const { return m_Speed; }
private: float m_Speed;};
// 캐릭터: 컴포넌트들을 조합class Character{public: explicit Character(const std::string& name) : m_Name(name) {}
template<typename T, typename... Args> T* AddComponent(Args&&... args) { auto comp = std::make_unique<T>(std::forward<Args>(args)...); T* ptr = comp.get(); m_Components.push_back(std::move(comp)); return ptr; }
void Update(float dt) { for (auto& comp : m_Components) { comp->Update(dt); } }
const std::string& GetName() const { return m_Name; }
private: std::string m_Name; std::vector<std::unique_ptr<Component>> m_Components;};
int main(){ Character player("Hero"); auto* health = player.AddComponent<HealthComponent>(200.f); auto* movement = player.AddComponent<MovementComponent>(300.f);
health->TakeDamage(50.f); std::cout << "Speed: " << movement->GetSpeed() << "\n"; std::cout << "Alive: " << (health->IsAlive() ? "Yes" : "No") << "\n";
return 0;}| 개념 | 핵심 요약 |
|---|---|
| class vs struct | 기본 접근 제어자만 다름. 동작 포함 시 class 사용 권장 |
| 가상함수 | virtual 선언으로 동적 바인딩 활성화, vtable 사용 |
| 순수 가상함수 | = 0 선언, 추상 클래스 생성, 파생 클래스에서 구현 강제 |
| override | 서명 불일치를 컴파일 타임에 잡아줌, 항상 붙이는 것 권장 |
| final | 상속 또는 오버라이드를 금지할 때 사용 |
| 기반 클래스 소멸자 | 반드시 virtual 선언 (다형성 삭제 시 파생 소멸자 호출 보장) |