Learning
레슨 8 / 10·2개 토픽

클래스와 객체지향

클래스 정의와 접근 지정자

C++에서 클래스는 데이터(멤버 변수)와 동작(멤버 함수)을 하나로 묶는 사용자 정의 타입입니다. public, private, protected 접근 지정자로 외부에서의 접근을 제어합니다.

cpp
#include <iostream>
#include <string>
using namespace std;

class BankAccount {
private:   // 외부 접근 불가
    string owner;
    double balance;

public:    // 외부 접근 가능
    // 생성자
    BankAccount(const string& name, double initial)
        : owner(name), balance(initial) {}

    // 소멸자
    ~BankAccount() {
        cout << owner << " 계좌 소멸" << endl;
    }

    // 복사 생성자
    BankAccount(const BankAccount& other)
        : owner(other.owner + " (복사)"), balance(other.balance) {}

    void deposit(double amount) {
        if (amount > 0) balance += amount;
    }

    bool withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            return true;
        }
        return false;
    }

    void showInfo() const {  // const 멤버 함수
        cout << owner << " | 잔액: " << balance << "원" << endl;
    }

protected: // 파생 클래스에서 접근 가능
    double getBalance() const { return balance; }
};

int main() {
    BankAccount acc("김철수", 10000);
    acc.deposit(5000);
    acc.withdraw(3000);
    acc.showInfo();  // 김철수 | 잔액: 12000원

    BankAccount copy(acc);  // 복사 생성자 호출
    copy.showInfo();  // 김철수 (복사) | 잔액: 12000원
    return 0;
}

상속과 가상 함수

상속은 기존 클래스(기반 클래스)를 확장하여 새로운 클래스(파생 클래스)를 만드는 메커니즘입니다. virtual 키워드로 선언한 함수는 파생 클래스에서 재정의(override)할 수 있으며, 기반 클래스 포인터를 통해 호출해도 실제 객체의 함수가 실행됩니다(다형성).

cpp
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;

// 추상 기반 클래스
class Shape {
protected:
    string name;

public:
    Shape(const string& n) : name(n) {}
    virtual ~Shape() = default;  // 가상 소멸자 필수

    // 순수 가상 함수 — 파생 클래스에서 반드시 구현
    virtual double area() const = 0;
    virtual double perimeter() const = 0;

    virtual void print() const {
        cout << name << " | 넓이: " << area()
             << " | 둘레: " << perimeter() << endl;
    }
};

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : Shape("원"), radius(r) {}
    double area() const override { return 3.14159 * radius * radius; }
    double perimeter() const override { return 2 * 3.14159 * radius; }
};

class Rectangle : public Shape {
    double width, height;
public:
    Rectangle(double w, double h)
        : Shape("직사각형"), width(w), height(h) {}
    double area() const override { return width * height; }
    double perimeter() const override { return 2 * (width + height); }
};

int main() {
    // 다형성: 기반 클래스 포인터로 파생 객체 사용
    vector<unique_ptr<Shape>> shapes;
    shapes.push_back(make_unique<Circle>(5.0));
    shapes.push_back(make_unique<Rectangle>(4.0, 6.0));

    for (const auto& s : shapes) {
        s->print();  // 각 도형의 실제 타입에 맞는 함수 호출
    }
    // 원 | 넓이: 78.5398 | 둘레: 31.4159
    // 직사각형 | 넓이: 24 | 둘레: 20
    return 0;
}
  • public — 어디서든 접근 가능
  • private — 해당 클래스 내부에서만 접근 가능 (기본값)
  • protected — 해당 클래스와 파생 클래스에서 접근 가능
  • 생성자 — 객체 생성 시 자동 호출, 초기화 리스트(:)로 멤버 초기화
  • 소멸자(~) — 객체 소멸 시 자동 호출, 리소스 해제 담당
  • virtual — 파생 클래스에서 재정의 가능한 함수 선언
  • = 0 — 순수 가상 함수, 해당 클래스를 추상 클래스로 만듦
  • override — 기반 클래스의 가상 함수를 재정의함을 명시
💡

상속 계층에서 기반 클래스의 소멸자는 반드시 virtual로 선언하세요. 그렇지 않으면 기반 클래스 포인터로 파생 객체를 삭제할 때 파생 클래스의 소멸자가 호출되지 않아 메모리 누수가 발생합니다.