포스트

CS — oop

OOP 4대 원칙과 SOLID를 C++ 문법과 짝지어 면접 답변 흐름으로 정리

CS — oop

모의면접 답변 — “OOP란 무엇인가요?”

OOP, 즉 객체지향 프로그래밍은 프로그램을 객체(Object) 단위로 구성하는 프로그래밍 패러다임입니다. 현실 세계의 사물처럼, 데이터(속성)와 행동(메서드)을 하나의 객체로 묶어 관리하며, 클래스는 설계도, 객체는 그 설계도로 만든 실체입니다.

OOP는 4가지 핵심 원칙으로 구성됩니다.

  • 첫째, 캡슐화입니다. 데이터와 메서드를 하나의 클래스로 묶고, private 접근 지정자로 내부 구현을 숨겨 데이터 무결성을 보장합니다.
  • 둘째, 상속입니다. 부모 클래스의 속성과 메서드를 자식 클래스가 물려받아 코드 재사용성을 높입니다.
  • 셋째, 다형성입니다. 같은 인터페이스로 다른 동작을 수행하는 능력으로, C++에서는 가상 함수(virtual)와 오버라이딩이 있습니다.
  • 넷째, 추상화입니다. 복잡한 구현 세부사항을 숨기고 핵심 인터페이스만 외부에 노출합니다. C++에서는 순수 가상 함수(= 0)로 추상 클래스를 구현합니다.

실제 예시로, 언리얼 엔진의 AActor나 UObject 모두 이 OOP 원칙을 기반으로 설계되어 있어, BeginPlay()와 Tick() 같은 가상 함수를 재정의해 다형성을 활용합니다.

기본 개념

  • 객체 (Object) — 데이터(속성)와 행동(메서드)을 묶은 단위
  • 클래스 (Class) — 객체를 만들기 위한 설계도 (붕어빵 틀)
  • 인스턴스 (Instance) — 클래스에서 생성된 실체 (붕어빵)
  • 패러다임 (Paradigm) — 프로그래밍을 바라보는 방식/관점
  • 속성 (Attribute) — 객체가 가지는 데이터 (멤버 변수)
  • 메서드 (Method) — 객체가 수행하는 행동 (멤버 함수)

4대 원칙

원칙핵심 키워드
캡슐화 (Encapsulation)private, 접근 지정자, 정보 은닉, 데이터 무결성, Getter/Setter
상속 (Inheritance)IS-A 관계, 부모/자식 클래스, 코드 재사용성, virtual, override
다형성 (Polymorphism)가상 함수(virtual), 오버라이딩, 런타임 결정, vtable, 동적 디스패치
추상화 (Abstraction)순수 가상 함수(= 0), 추상 클래스, 인터페이스, 계약(Contract)

언리얼 연결 키워드

  • AActor / UObject — 언리얼의 핵심 클래스, OOP 기반 설계
  • BeginPlay() / Tick() — 가상 함수 → 다형성 실제 활용 예시
  • UPROPERTY() — 캡슐화의 연장선 (에디터 노출 범위 제어)
  • IInterface — 언리얼의 인터페이스 패턴 (추상화)

new는 함수인가?

new는 함수가 아니라 C++ 연산자(operator)입니다. 단, 내부적으로 메모리 할당을 위해 operator new라는 함수를 호출합니다. operator new는 오버로딩 가능한 함수이고, new 표현식은 그것을 감싸는 연산자입니다.

new 표현식의 실행 단계

1
2
3
4
5
Player* p = new Player();
// 내부적으로:
void* mem = operator new(sizeof(Player));  // 1. 메모리 할당
new(mem) Player();                          // 2. 생성자 호출 (placement new)
Player* p = static_cast<Player*>(mem);      // 3. 타입 포인터로 반환

operator new vs new 표현식

 new 표현식operator new
분류연산자함수
역할메모리 할당 + 생성자 호출메모리 할당만
오버로딩불가가능
직접 호출new T()operator new(sizeof(T))

객체(Object)란?

1
2
3
4
5
6
7
8
9
10
11
12
13
// 클래스 = 설계도 (붕어빵 틀)
class Player {
public:
    string name;   // 속성
    int health;

    void Attack() { }  // 행동
    void Move()   { }
};

// 객체 = 인스턴스 (붕어빵)
Player p1;
Player p2;  // p2는 별개의 객체 — 같은 설계도, 다른 실체

캡슐화 (Encapsulation)

캡슐화는 데이터(멤버 변수)와 메서드(멤버 함수)를 하나의 클래스로 묶고, 내부 구현을 외부에서 직접 접근하지 못하도록 정보 은닉(Information Hiding)하는 것입니다. 접근 지정자(public / protected / private)로 외부 노출 범위를 제어합니다.

접근 지정자

  • public: 클래스 내부 + 외부 + 파생 클래스 — 외부 인터페이스
  • protected: 클래스 내부 + 파생 클래스 — 파생 클래스 전용
  • private: 클래스 내부만 — 구현 세부사항 은닉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class BankAccount {
private:
    double balance;

public:
    void Deposit(double amount) {
        if (amount > 0)
            balance += amount;
    }
    void Withdraw(double amount) {
        if (amount > 0 && amount <= balance)
            balance -= amount;
    }
    double GetBalance() const { return balance; }
};

BankAccount acc;
// acc.balance = -500;   // ERROR
acc.Deposit(1000);       // OK — 검증 후 안전 처리

상속 (Inheritance)

상속은 기존 클래스(부모/기반 클래스)의 속성과 메서드를 새 클래스(자식/파생 클래스)가 물려받아 재사용하는 메커니즘입니다. 코드 재사용성을 높이고, IS-A 관계를 표현합니다.

IS-A vs HAS-A

  • IS-A: public 상속 (Dog IS-A Animal)
  • HAS-A: 컴포지션 or private 상속 (Car HAS-A Engine)

상속 방식별 접근 지정자 변환

기본 클래스 멤버public 상속protected 상속private 상속
public→ public→ protected→ private
protected→ protected→ protected→ private
private접근 불가접근 불가접근 불가

가상 함수 + override

1
2
3
4
5
6
7
8
9
10
class Shape {
public:
    virtual void Draw() = 0;   // 순수 가상 함수
    virtual ~Shape() { }       // 가상 소멸자 필수!
};

class Circle : public Shape {
public:
    void Draw() override { cout << "원 그리기\n"; }
};

복기:

  • IS-A = public 상속, HAS-A = 컴포지션
  • virtual 없으면 파생 클래스에서 재정의해도 다형성 불가
  • 가상 소멸자 없으면 파생 클래스 소멸자 미호출 → 메모리 누수
  • override 키워드로 재정의 명시 → 오타를 컴파일 에러로

다형성 (Polymorphism)

다형성은 같은 인터페이스(함수 이름)로 다른 동작을 수행하는 능력입니다. C++에서는 컴파일 타임 다형성(오버로딩, 템플릿)과 런타임 다형성(가상 함수, 오버라이딩)으로 나뉩니다.

컴파일 타임 vs 런타임

 컴파일 타임런타임
구현 방법함수 오버로딩, 템플릿가상 함수, 오버라이딩
결정 시점컴파일 시런타임 시 (vtable)
성능빠름 (인라인 가능)vtable 참조 비용 발생
유연성낮음높음 (동적 디스패치)

함수 오버로딩 (컴파일 타임)

1
2
3
4
5
6
7
void Attack(int damage)          { cout << "물리 공격: " << damage; }
void Attack(float damage)        { cout << "마법 공격: " << damage; }
void Attack(int damage, int cnt) { cout << "연속 공격: " << damage * cnt; }

Attack(50);        // Attack(int)
Attack(30.5f);     // Attack(float)
Attack(20, 3);     // Attack(int, int)

가상 함수 (런타임)

1
2
3
4
5
6
7
8
9
10
11
12
class Shape {
public:
    virtual void Draw() { cout << "도형\n"; }
    virtual ~Shape() { }
};

class Circle   : public Shape { public: void Draw() override { cout << "원\n"; } };
class Triangle : public Shape { public: void Draw() override { cout << "삼각형\n"; } };

Shape* shapes[] = { new Circle(), new Triangle() };
for (Shape* s : shapes)
    s->Draw();  // "원" → "삼각형"

vtable 동작

  • 가상 함수 있으면 컴파일러가 vtable 자동 생성
  • 객체는 vptr(vtable 포인터)을 숨겨서 보유
  • s->Draw() 호출 시: vptr 읽기 → vtable에서 함수 포인터 찾기 → 간접 호출
  • 이것이 동적 디스패치(Dynamic Dispatch)

추상화 (Abstraction)

추상화는 복잡한 구현 세부사항을 숨기고 핵심 인터페이스만 외부에 노출하는 것입니다. C++에서는 순수 가상 함수(= 0)로 추상 클래스를 만들어 구현합니다.

추상 클래스 vs 인터페이스

 추상 클래스인터페이스
C++ 구현순수 가상 함수 1개 이상모든 함수가 순수 가상 함수
멤버 변수가질 수 있음없음 (권장)
인스턴스화불가불가
용도공통 구현 + 일부 추상화계약(Contract)만 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal {
public:
    virtual void MakeSound() = 0;  // 순수 가상 함수
    virtual ~Animal() { }
    void Breathe() { cout << "숨쉬기\n"; }
};

class Dog : public Animal {
public:
    void MakeSound() override { cout << "멍멍!\n"; }
};

// Animal a;   // ERROR — 추상 클래스는 인스턴스화 불가
Dog d;
d.MakeSound(); // "멍멍!"

다중 인터페이스 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class IAttackable {
public:
    virtual void Attack() = 0;
    virtual int  GetDamage() const = 0;
    virtual ~IAttackable() { }
};

class IMovable {
public:
    virtual void Move(float x, float y) = 0;
    virtual ~IMovable() { }
};

class Player : public IAttackable, public IMovable {
public:
    void Attack() override           { }
    int  GetDamage() const override  { return 50; }
    void Move(float x, float y) override { }
};

OOP vs 절차지향 비교

항목절차지향OOP
구조 단위함수객체
데이터 접근전역/공유 직접 접근캡슐화로 보호
코드 재사용함수 재사용상속·컴포지션
유지보수규모 커질수록 어려움캡슐화로 변경 영향 제한
언어 예시C, Pascal, FORTRANC++, Java, Python, C#
적합단순 스크립트대규모 시스템, 게임 엔진

SOLID 원칙

SOLID는 유지보수 가능한 OOP 설계를 위한 5가지 원칙입니다. 로버트 C. 마틴이 정리하였습니다.

원칙이름핵심 내용
S단일 책임 원칙 (SRP)클래스는 하나의 책임만 가져야 한다
O개방-폐쇄 원칙 (OCP)확장에는 열려있고, 수정에는 닫혀있어야 한다
L리스코프 치환 원칙 (LSP)자식 클래스는 부모 클래스로 대체 가능해야 한다
I인터페이스 분리 원칙 (ISP)클라이언트는 사용하지 않는 인터페이스에 의존하지 않아야 한다
D의존성 역전 원칙 (DIP)고수준 모듈은 저수준 모듈이 아닌 추상화에 의존해야 한다

SRP

1
2
3
4
5
6
7
8
9
10
11
// 나쁜 예
class Player {
    void Move()     { }
    void SaveData() { }   // 다른 책임!
    void Render()   { }   // 또 다른 책임!
};

// 좋은 예
class Player         { void Move()  { } };
class PlayerSaver    { void Save(const Player& p)   { } };
class PlayerRenderer { void Render(const Player& p) { } };

OCP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 나쁜 예 — 새 무기 추가 시 기존 코드 수정
void UseWeapon(string type) {
    if (type == "sword")     { }
    else if (type == "bow")  { }
}

// 좋은 예 — 추상화로 확장 가능
class IWeapon {
public:
    virtual void Use() = 0;
    virtual ~IWeapon() { }
};
class Sword : public IWeapon { public: void Use() override { } };
class Bow   : public IWeapon { public: void Use() override { } };

복기:

  • SRP: 클래스 하나 = 책임 하나
  • OCP: 기존 코드 수정 없이 새 기능 추가 (추상화 활용)
  • LSP: 부모 타입 포인터로 자식 사용 시 동작이 예상과 같아야 함
  • ISP: 뚱뚱한 인터페이스 → 작은 인터페이스 여러 개로 분리
  • DIP: 구체 클래스가 아닌 인터페이스(추상화)에 의존
  • 언리얼: IInterface, UActorComponent 분리 설계 = SOLID 실천

핵심 요약 — OOP 4대 원칙(캡슐화·상속·다형성·추상화)을 각각 C++ 문법(접근 지정자, virtual/override, 순수 가상 함수 = 0)과 짝지어 정리했고, SOLID 원칙을 언리얼의 IInterface·컴포넌트 분리 설계와 연결해서 이해했다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.