모의면접 답변 — “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, FORTRAN | C++, 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·컴포넌트 분리 설계와 연결해서 이해했다.