1. RTTI와 RAII
RTTI와 RAII는 C++의 중요한 개념으로, RTTI는 객체의 타입을 식별하는 데 중점을 두고, RAII는 자원의 생명주기를 관리하는 데 중점을 둡니다.
1.1. RTTI (Run-Time Type Information)
- 목적: 프로그램 실행 중에 다형성을 가진 객체의 실제 동적 타입(dynamic type)이 무엇인지 식별하는 메커니즘입니다.
- 주요 기능: 기반 클래스 포인터나 참조를 통해 파생 클래스 객체를 가리킬 때, 해당 객체의 원래 타입을 알아낼 수 있습니다.
- 핵심 연산자:
dynamic_cast(안전한 다운캐스팅),typeid(객체의 타입 정보 획득). - 핵심 문제: "이 객체의 실제 타입은 무엇인가?"
1.2. RAII (Resource Acquisition Is Initialization)
- 목적: 자원의 생명주기를 객체의 생명주기에 바인딩하여 자원 관리를 자동화하고, 자원 누수를 방지하는 프로그래밍 기법(idiom)입니다.
- 주요 기능: 객체가 생성될 때(생성자) 자원을 획득하고, 객체가 소멸될 때(소멸자) 자원을 자동으로 해제합니다. 예외가 발생해도 소멸자는 반드시 호출되므로, 예외 안전성을 보장하는 데 핵심적인 역할을 합니다.
- 핵심 구현: 클래스의 생성자/소멸자, 스마트 포인터(
std::unique_ptr,std::shared_ptr). - 핵심 문제: "어떻게 하면 자원을 절대 잊지 않고, 예외가 발생해도 안전하게 해제할 수 있는가?"
2. RTTI와 RAII 상세 비교
두 개념은 문제 영역과 해결 방식이 근본적으로 다릅니다.
| 구분 | RTTI (Run-Time Type Information) | RAII (Resource Acquisition Is Initialization) |
|---|---|---|
| 주된 목적 | 객체의 타입 식별 (Identification) | 자원의 생명주기 관리 (Management) |
| 동작 시점 | 실행 시간 (Run-time). dynamic_cast 등은 실행 중에 타입을 검사한다. |
컴파일 시간 및 실행 시간. 객체의 생명주기는 컴파일 시점에 결정되며(스코프 기반), 소멸자는 실행 중 스코프를 벗어날 때 호출된다. |
| 관련 C++ 기능 | dynamic_cast, typeid, 가상 함수(virtual functions), vtable |
생성자(Constructor), 소멸자(Destructor), 스마트 포인터(unique_ptr, shared_ptr) |
| 문제 해결 영역 | 다형성(Polymorphism)을 활용하는 객체지향 설계에서 특정 파생 클래스의 고유 기능에 접근해야 할 때 사용한다. | 메모리, 파일 핸들, 소켓, 뮤텍스 등 모든 종류의 한정된 자원을 다룰 때 사용한다. |
| 성능 영향 | dynamic_cast는 런타임에 상속 계층을 탐색하므로 약간의 성능 오버헤드가 발생할 수 있다. |
일반적으로 제로 코스트 추상화(Zero-cost abstraction)에 가깝다. 컴파일러 최적화를 통해 인라인 처리되는 경우가 많아 성능 저하가 거의 없다. |
| 설계적 관점 | 남용할 경우, 가상 함수로 풀어야 할 문제를 잘못된 방식으로 접근하고 있다는 설계 문제의 신호일 수 있다. | C++에서 자원을 관리하는 가장 표준적이고 권장되는 핵심 패턴이다. |
계층 구조에서의 동작
- RTTI: 사용자 코드(
dynamic_cast) -> C++ 런타임(vtable 조회) -> 컴파일러가 생성한 타입 정보. 주로 C++ 런타임 시스템 수준에서 동작합니다. - RAII: 사용자 코드(객체 선언) -> C++ 컴파일러/런타임(스코프에 따른 생성자/소멸자 호출) -> 소멸자 코드(자원 해제 API 호출) -> OS(자원 반납). 언어의 핵심 규칙(객체 생명주기)을 활용하여 OS 자원까지 안정적으로 관리합니다.
3. 실습 코드
#include <iostream>
#include <vector>
#include <string>
#include <memory> // 스마트 포인터를 위해 포함
// 게임 세계의 모든 객체를 나타내는 기반 클래스
class GameEntity {
public:
GameEntity(const std::string& name) : name(name) {}
virtual ~GameEntity() {
std::cout << name << "(GameEntity) 소멸자 호출" << std::endl;
}
virtual void interact() {
std::cout << name << "과(와) 상호작용했지만 특별한 일은 없었다." << std::endl;
}
std::string getName() const { return name; }
protected:
std::string name;
};
// 플레이어 클래스
class Player : public GameEntity {
public:
Player(const std::string& name, int level) : GameEntity(name), level(level) {}
~Player() override {
std::cout << name << "(Player) 소멸자 호출" << std::endl;
}
void levelUp() {
level++;
std::cout << name << "의 레벨이 " << level << "이 되었습니다!" << std::endl;
}
private:
int level;
};
// 보물 상자 클래스
class TreasureChest : public GameEntity {
public:
TreasureChest(const std::string& name, bool is_locked) : GameEntity(name), is_locked(is_locked) {}
~TreasureChest() override {
std::cout << name << "(TreasureChest) 소멸자 호출" << std::endl;
}
void open() {
if (is_locked) {
std::cout << name << "은(는) 잠겨있다." << std::endl;
} else {
std::cout << name << "을(를) 열어 보물을 획득했다!" << std::endl;
}
}
private:
bool is_locked;
};
// 게임 월드: 엔티티들을 관리
void game_world() {
std::vector<std::unique_ptr<GameEntity>> entities;
// RAII: unique_ptr을 사용해 동적 할당된 객체의 생명주기를 관리
entities.push_back(std::make_unique<Player>("용사", 10));
entities.push_back(std::make_unique<TreasureChest>("오래된 상자", true));
entities.push_back(std::make_unique<TreasureChest>("새로운 상자", false));
std::cout << "\n[ 상호작용 시작 ]\n";
for (const auto& entity_ptr : entities) {
// RTTI: dynamic_cast를 사용해 entity_ptr이 실제로 Player를 가리키는지 확인
if (Player* player = dynamic_cast<Player*>(entity_ptr.get())) {
std::cout << player->getName() << "의 차례: ";
player->levelUp();
}
// RTTI: dynamic_cast를 사용해 TreasureChest 타입인지 확인
else if (TreasureChest* chest = dynamic_cast<TreasureChest*>(entity_ptr.get())) {
std::cout << chest->getName() << " 발견: ";
chest->open();
}
}
std::cout << "\n[ game_world 함수 종료, RAII로 인한 자동 소멸 시작 ]\n";
// 함수가 종료되면 'entities' 벡터가 소멸되고, 벡터 안의 모든 unique_ptr들이 소멸자를 호출한다.
// 결과적으로 new로 할당했던 모든 객체들이 delete 키워드 없이도 자동으로 해제된다. (RAII)
}
int main() {
game_world();
return 0;
}
4. 요약
RTTI와 RAII는 C++에서 완전히 다른 목적을 가진 개념입니다.
RTTI는 타입에 관한 것으로, "Run-Time Type Information"의 약자입니다. 프로그램 실행 중에 객체의 실제 타입이 무엇인지 확인해야 할 때, 예를 들어 부모 클래스 포인터로 자식 객체를 가리키고 있을 때 dynamic_cast를 써서 "이 객체가 정말 Player 타입이 맞아?"라고 물어보는 데 사용합니다. 즉, 객체의 정체를 파악하는 역할을 합니다.
RAII는 자원 관리에 관한 프로그래밍 기법으로, "Resource Acquisition Is Initialization"의 약자입니다. 메모리, 파일, 락과 같은 자원을 객체의 생명주기에 묶는 것입니다. 객체가 생성될 때 자원을 할당하고, 객체가 스코프를 벗어나 소멸될 때 자원을 자동으로 해제하도록 만들어, 자원 누수를 원천적으로 방지하고 예외가 발생해도 안전하게 코드를 작성할 수 있게 해줍니다. std::unique_ptr나 std::shared_ptr 같은 스마트 포인터가 대표적인 RAII의 예시입니다.
'C++' 카테고리의 다른 글
| 템플릿(Template)과 매크로(Macro) (0) | 2025.11.21 |
|---|---|
| 객체 지향 프로그래밍 (0) | 2025.11.20 |
| 인라인 함수 Inline Function (0) | 2025.11.19 |
| 실수 자료형을 사용할 때 발생할 수 있는 문제 (0) | 2025.11.19 |
| class와 struct의 기능 호환성 (1) | 2025.11.13 |
