Topic 4: 스마트 포인터와 현대적 C++ 🎯
🎯 학습 목표
- 메모리 누수 문제와 스마트 포인터의 필요성 이해하기
- unique_ptr, shared_ptr, weak_ptr의 특징과 사용법 익히기
- RAII 패턴으로 안전한 리소스 관리하기
- 현대적 C++ 스타일의 메모리 관리 적용하기
- 자동 메모리 관리 시스템 구현하기
🤖 스마트 포인터가 뭐예요?
스마트 포인터는 마치 자동 청소 로봇과 같아요! 일반 포인터로 동적 메모리를 관리하는 것은 집안일을 직접 해야 하는 것과 같지만, 스마트 포인터는 자동으로 메모리 정리를 해주는 똑똑한 도우미예요.
💡 비유: 애완동물을 키울 때, 먹이주기와 산책은 자동으로 되지 않지만, 자동 급식기와 로봇 청소기가 있다면 관리가 훨씬 쉬워지죠!
💔 메모리 누수 문제
#include <iostream>
using namespace std;
class Player {
public:
string name;
int level;
Player(string n, int l) : name(n), level(l) {
cout << "🎮 " << name << " 플레이어 생성 (Lv." << level << ")\n";
}
~Player() {
cout << "👋 " << name << " 플레이어 소멸\n";
}
};
void badExample() {
cout << "❌ 나쁜 예제 (메모리 누수):\n";
Player* player = new Player("BadPlayer", 10);
// 어떤 작업...
if (player->level < 15) {
cout << "⚠️ 레벨이 낮아서 조기 종료!\n";
return; // 🚨 delete를 호출하지 않고 함수 종료!
}
delete player; // 이 라인에 도달하지 않음!
}
void goodExample() {
cout << "\n✅ 좋은 예제 (자동 메모리 관리):\n";
// 스택에 생성 - 자동으로 소멸자 호출됨
Player player("GoodPlayer", 10);
if (player.level < 15) {
cout << "⚠️ 레벨이 낮아서 조기 종료!\n";
return; // 자동으로 소멸자가 호출됨!
}
}
int main() {
badExample();
goodExample();
cout << "\n💡 스마트 포인터가 이런 문제를 해결해줘요!\n";
return 0;
}🎯 unique_ptr - 독점 소유권
#include <iostream>
#include <memory> // 스마트 포인터 헤더
using namespace std;
class Weapon {
public:
string name;
int damage;
Weapon(string n, int d) : name(n), damage(d) {
cout << "⚔️ " << name << " 무기 생성 (공격력: " << damage << ")\n";
}
~Weapon() {
cout << "💥 " << name << " 무기 소멸\n";
}
void attack() {
cout << "⚡ " << name << "으로 " << damage << " 데미지 공격!\n";
}
};
int main() {
cout << "🎯 unique_ptr 예제:\n\n";
// unique_ptr 생성 (추천하는 방법)
auto sword = make_unique<Weapon>("Fire Sword", 150);
// 일반 포인터처럼 사용
sword->attack();
cout << "🔍 무기 이름: " << sword->name << "\n";
// 소유권 이전 (move)
auto newOwner = move(sword);
if (!sword) {
cout << "📤 sword는 이제 nullptr입니다!\n";
}
newOwner->attack();
// 새로운 무기로 교체
newOwner.reset(new Weapon("Ice Staff", 120));
// 다른 함수로 전달 예제
auto bow = make_unique<Weapon>("Magic Bow", 100);
cout << "\n🔄 함수 전달 전:\n";
if (bow) {
bow->attack();
}
// 함수에 unique_ptr 전달하는 방법들
auto processWeapon = [](unique_ptr<Weapon> weapon) {
cout << "📦 함수에서 " << weapon->name << " 처리 중...\n";
weapon->attack();
// 함수 종료 시 자동으로 소멸됨
};
processWeapon(move(bow)); // 소유권 완전 이전
cout << "\n🔄 함수 전달 후:\n";
if (!bow) {
cout << "📤 bow는 이제 nullptr입니다!\n";
}
cout << "\n💡 함수 종료 시 모든 unique_ptr 자동 소멸!\n";
return 0;
} // 여기서 newOwner 자동 소멸👥 shared_ptr - 공유 소유권
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class Spell {
public:
string name;
int manaCost;
Spell(string n, int cost) : name(n), manaCost(cost) {
cout << "✨ " << name << " 마법 생성 (마나 비용: " << cost << ")\n";
}
~Spell() {
cout << "💫 " << name << " 마법 소멸\n";
}
void cast() {
cout << "🔮 " << name << " 시전! (마나 " << manaCost << " 소모)\n";
}
};
class Wizard {
public:
string name;
vector<shared_ptr<Spell>> spells;
Wizard(string n) : name(n) {
cout << "🧙♂️ " << name << " 마법사 등장!\n";
}
void learnSpell(shared_ptr<Spell> spell) {
spells.push_back(spell);
cout << "📚 " << name << "가 " << spell->name << " 마법을 배웠다!\n";
cout << "📊 현재 참조 횟수: " << spell.use_count() << "\n";
}
void castSpell(int index) {
if (index >= 0 && index < spells.size()) {
spells[index]->cast();
}
}
};
int main() {
cout << "👥 shared_ptr 예제:\n\n";
// shared_ptr 생성
auto fireball = make_shared<Spell>("Fireball", 30);
auto heal = make_shared<Spell>("Heal", 20);
cout << "🔍 Fireball 참조 횟수: " << fireball.use_count() << "\n\n";
// 여러 마법사가 같은 마법 공유
Wizard wizard1("Gandalf");
Wizard wizard2("Merlin");
Wizard wizard3("Harry");
// 모든 마법사가 Fireball 학습
wizard1.learnSpell(fireball);
wizard2.learnSpell(fireball);
wizard3.learnSpell(fireball);
cout << "\n🔥 모든 마법사가 Fireball 사용:\n";
wizard1.castSpell(0);
wizard2.castSpell(0);
wizard3.castSpell(0);
// 일부 마법사만 Heal 학습
wizard1.learnSpell(heal);
wizard2.learnSpell(heal);
cout << "\n💚 일부 마법사가 Heal 사용:\n";
wizard1.castSpell(1);
wizard2.castSpell(1);
cout << "\n📊 최종 참조 횟수:\n";
cout << "🔥 Fireball: " << fireball.use_count() << "개\n";
cout << "💚 Heal: " << heal.use_count() << "개\n";
cout << "\n💡 함수 종료 시 참조 횟수가 0이 되면 자동 소멸!\n";
return 0;
}🔄 weak_ptr - 순환 참조 해결
#include <iostream>
#include <memory>
using namespace std;
class Guild; // 전방 선언
class Player {
public:
string name;
shared_ptr<Guild> guild; // 길드에 대한 강한 참조
Player(string n) : name(n) {
cout << "👤 플레이어 " << name << " 생성\n";
}
~Player() {
cout << "👋 플레이어 " << name << " 소멸\n";
}
void joinGuild(shared_ptr<Guild> g) {
guild = g;
cout << "🏰 " << name << "이 길드에 가입했습니다!\n";
}
};
class Guild {
public:
string name;
vector<weak_ptr<Player>> members; // 플레이어에 대한 약한 참조 (중요!)
Guild(string n) : name(n) {
cout << "🏰 길드 " << name << " 설립\n";
}
~Guild() {
cout << "💥 길드 " << name << " 해체\n";
}
void addMember(shared_ptr<Player> player) {
members.push_back(player); // weak_ptr로 저장
player->joinGuild(shared_from_this());
}
void showMembers() {
cout << "📋 " << name << " 길드원 목록:\n";
for (auto& weakPlayer : members) {
if (auto player = weakPlayer.lock()) { // weak_ptr을 shared_ptr로 변환
cout << " 👤 " << player->name << "\n";
} else {
cout << " 👻 (이미 떠난 플레이어)\n";
}
}
}
// shared_from_this를 사용하기 위해 필요
shared_ptr<Guild> shared_from_this() {
// 실제로는 enable_shared_from_this를 상속받아야 하지만,
// 여기서는 간단히 구현
static shared_ptr<Guild> self;
if (!self) {
self = shared_ptr<Guild>(this, [](Guild*){});
}
return self;
}
};
int main() {
cout << "🔄 weak_ptr 순환 참조 해결 예제:\n\n";
// 길드 생성
auto dragonGuild = make_shared<Guild>("Dragon Slayers");
{
// 플레이어들 생성 (블록 스코프)
auto player1 = make_shared<Player>("Alice");
auto player2 = make_shared<Player>("Bob");
// 길드 가입
dragonGuild->addMember(player1);
dragonGuild->addMember(player2);
cout << "\n📊 길드원 확인:\n";
dragonGuild->showMembers();
cout << "\n🔍 참조 횟수 확인:\n";
cout << "👤 Alice: " << player1.use_count() << "\n";
cout << "👤 Bob: " << player2.use_count() << "\n";
cout << "🏰 Guild: " << dragonGuild.use_count() << "\n";
cout << "\n💡 플레이어들이 스코프를 벗어납니다...\n";
}
cout << "\n📊 플레이어 소멸 후 길드원 확인:\n";
dragonGuild->showMembers();
cout << "\n🔍 길드 참조 횟수: " << dragonGuild.use_count() << "\n";
// weak_ptr 사용법 예제
cout << "\n🎯 weak_ptr 직접 사용 예제:\n";
shared_ptr<Player> tempPlayer = make_shared<Player>("Temp");
weak_ptr<Player> weakRef = tempPlayer;
cout << "💪 강한 참조 존재: " << (weakRef.expired() ? "No" : "Yes") << "\n";
if (auto lockedPlayer = weakRef.lock()) {
cout << "🔒 weak_ptr에서 접근: " << lockedPlayer->name << "\n";
}
tempPlayer.reset(); // 강한 참조 제거
cout << "💪 강한 참조 존재: " << (weakRef.expired() ? "No" : "Yes") << "\n";
return 0;
}🛡️ RAII 패턴과 리소스 관리
#include <iostream>
#include <memory>
#include <fstream>
using namespace std;
// RAII 패턴을 활용한 게임 리소스 관리자
class GameResource {
public:
string resourceName;
GameResource(string name) : resourceName(name) {
cout << "📂 " << resourceName << " 리소스 로드\n";
}
~GameResource() {
cout << "🗑️ " << resourceName << " 리소스 해제\n";
}
void use() {
cout << "⚡ " << resourceName << " 사용 중...\n";
}
};
class FileManager {
private:
unique_ptr<ofstream> file;
public:
FileManager(const string& filename) {
file = make_unique<ofstream>(filename);
if (file->is_open()) {
cout << "📁 " << filename << " 파일 열기 성공\n";
} else {
cout << "❌ " << filename << " 파일 열기 실패\n";
}
}
~FileManager() {
if (file && file->is_open()) {
file->close();
cout << "📁 파일 자동 닫기\n";
}
}
void write(const string& data) {
if (file && file->is_open()) {
*file << data << "\n";
cout << "✍️ 데이터 쓰기: " << data << "\n";
}
}
};
// 게임 세션 매니저 - 복합 리소스 관리
class GameSession {
private:
unique_ptr<GameResource> bgMusic;
unique_ptr<GameResource> graphics;
unique_ptr<FileManager> saveFile;
public:
GameSession(const string& sessionName) {
cout << "🎮 게임 세션 '" << sessionName << "' 시작\n";
cout << "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
// 모든 리소스 자동 초기화
bgMusic = make_unique<GameResource>("Background Music");
graphics = make_unique<GameResource>("Graphics Engine");
saveFile = make_unique<FileManager>(sessionName + "_save.txt");
cout << "✅ 모든 리소스 로드 완료!\n\n";
}
~GameSession() {
cout << "\n🏁 게임 세션 종료\n";
cout << "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
// 소멸자에서 모든 리소스가 자동으로 해제됨
cout << "🧹 모든 리소스 자동 정리 완료!\n";
}
void playGame() {
cout << "🎵 게임 플레이 중...\n";
bgMusic->use();
graphics->use();
saveFile->write("Player reached level 5");
saveFile->write("Score: 1500");
// 예외가 발생해도 RAII에 의해 리소스가 안전하게 해제됨
if (rand() % 100 < 10) { // 10% 확률로 예외 발생 시뮬레이션
throw runtime_error("게임 크래시!");
}
}
};
int main() {
cout << "🛡️ RAII 패턴과 스마트 포인터 예제\n\n";
try {
GameSession session("Adventure_Mode");
session.playGame();
cout << "\n🎯 정상 게임 완료!\n";
} catch (const exception& e) {
cout << "\n⚠️ 예외 발생: " << e.what() << "\n";
cout << "💡 하지만 RAII에 의해 리소스는 안전하게 정리됩니다!\n";
}
cout << "\n" << "=".repeat(50) << "\n";
cout << "🔄 스마트 포인터의 장점 요약:\n";
cout << " ✅ 자동 메모리 관리\n";
cout << " ✅ 예외 안전성\n";
cout << " ✅ 메모리 누수 방지\n";
cout << " ✅ 명확한 소유권 관계\n";
cout << " ✅ 현대적 C++ 스타일\n";
return 0;
}🎮 실전 프로젝트: 자동 메모리 관리 시스템
#include <iostream>
#include <memory>
#include <vector>
#include <map>
#include <string>
#include <algorithm>
using namespace std;
// 기본 게임 엔티티
class Entity {
protected:
static int nextId;
int id;
string name;
public:
Entity(const string& entityName) : id(++nextId), name(entityName) {
cout << "🌟 Entity [" << id << "] " << name << " 생성\n";
}
virtual ~Entity() {
cout << "💥 Entity [" << id << "] " << name << " 소멸\n";
}
int getId() const { return id; }
const string& getName() const { return name; }
virtual void update() {
cout << "🔄 " << name << " 업데이트\n";
}
};
int Entity::nextId = 0;
// 플레이어 클래스
class Player : public Entity {
private:
int health;
int score;
public:
Player(const string& playerName, int hp = 100)
: Entity("Player-" + playerName), health(hp), score(0) {
}
void takeDamage(int damage) {
health -= damage;
cout << "💔 " << name << " 체력 " << damage << " 감소 (현재: " << health << ")\n";
}
void addScore(int points) {
score += points;
cout << "⭐ " << name << " 점수 +" << points << " (총: " << score << "점)\n";
}
bool isAlive() const { return health > 0; }
int getHealth() const { return health; }
int getScore() const { return score; }
void update() override {
if (isAlive()) {
cout << "🏃 " << name << " 활동 중 (HP: " << health << ")\n";
} else {
cout << "💀 " << name << " 사망\n";
}
}
};
// 적 클래스
class Enemy : public Entity {
private:
int attackPower;
weak_ptr<Player> target; // 플레이어에 대한 약한 참조
public:
Enemy(const string& enemyType, int power)
: Entity("Enemy-" + enemyType), attackPower(power) {
}
void setTarget(shared_ptr<Player> player) {
target = player;
cout << "🎯 " << name << "이 " << player->getName() << " 타겟 설정\n";
}
void attack() {
if (auto player = target.lock()) { // weak_ptr을 shared_ptr로 변환
cout << "⚔️ " << name << "이 공격!\n";
player->takeDamage(attackPower);
} else {
cout << "👻 " << name << "의 타겟이 사라짐\n";
}
}
void update() override {
attack();
}
};
// 아이템 클래스
class Item : public Entity {
private:
string type;
int value;
public:
Item(const string& itemType, int itemValue)
: Entity("Item-" + itemType), type(itemType), value(itemValue) {
}
void use(shared_ptr<Player> player) {
cout << "✨ " << name << " 사용!\n";
if (type == "Potion") {
// 포션은 체력 회복 (간단히 점수로 대체)
player->addScore(value);
}
}
};
// 게임 월드 매니저
class GameWorld {
private:
vector<shared_ptr<Entity>> entities;
map<int, weak_ptr<Entity>> entityLookup;
// 씬 매니저 - 특정 타입별 관리
vector<shared_ptr<Player>> players;
vector<shared_ptr<Enemy>> enemies;
vector<shared_ptr<Item>> items;
public:
GameWorld() {
cout << "🌍 게임 월드 생성\n";
}
~GameWorld() {
cout << "🌍 게임 월드 소멸\n";
}
// 플레이어 생성
shared_ptr<Player> createPlayer(const string& name) {
auto player = make_shared<Player>(name);
entities.push_back(player);
players.push_back(player);
entityLookup[player->getId()] = player;
cout << "👤 플레이어 '" << name << "' 월드에 추가\n";
return player;
}
// 적 생성
shared_ptr<Enemy> createEnemy(const string& type, int power) {
auto enemy = make_shared<Enemy>(type, power);
entities.push_back(enemy);
enemies.push_back(enemy);
entityLookup[enemy->getId()] = enemy;
cout << "👹 적 '" << type << "' 월드에 생성\n";
return enemy;
}
// 아이템 생성
shared_ptr<Item> createItem(const string& type, int value) {
auto item = make_shared<Item>(type, value);
entities.push_back(item);
items.push_back(item);
entityLookup[item->getId()] = item;
cout << "💎 아이템 '" << type << "' 월드에 생성\n";
return item;
}
// 엔티티 제거 (약한 참조를 통한 안전한 제거)
void removeEntity(int entityId) {
if (auto entity = entityLookup[entityId].lock()) {
cout << "🗑️ 엔티티 [" << entityId << "] 제거 요청\n";
// 벡터에서 제거
entities.erase(
remove_if(entities.begin(), entities.end(),
[entityId](const shared_ptr<Entity>& e) {
return e->getId() == entityId;
}),
entities.end()
);
entityLookup.erase(entityId);
cout << "✅ 엔티티 [" << entityId << "] 제거 완료\n";
}
}
// 월드 업데이트
void update() {
cout << "\n🔄 월드 업데이트 시작\n";
cout << "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
// 모든 엔티티 업데이트
for (auto& entity : entities) {
entity->update();
}
// 죽은 플레이어 제거
players.erase(
remove_if(players.begin(), players.end(),
[this](const shared_ptr<Player>& p) {
if (!p->isAlive()) {
removeEntity(p->getId());
return true;
}
return false;
}),
players.end()
);
cout << "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
}
// 통계 출력
void showStats() {
cout << "\n📊 게임 월드 통계:\n";
cout << " 🌟 총 엔티티 수: " << entities.size() << "\n";
cout << " 👤 플레이어 수: " << players.size() << "\n";
cout << " 👹 적 수: " << enemies.size() << "\n";
cout << " 💎 아이템 수: " << items.size() << "\n";
cout << "\n📋 메모리 참조 상태:\n";
for (const auto& pair : entityLookup) {
if (auto entity = pair.second.lock()) {
cout << " ✅ Entity [" << pair.first << "]: 활성\n";
} else {
cout << " ❌ Entity [" << pair.first << "]: 소멸됨\n";
}
}
}
// 게임 시뮬레이션
void simulate() {
cout << "\n🎮 게임 시뮬레이션 시작!\n";
// 플레이어들 생성
auto alice = createPlayer("Alice");
auto bob = createPlayer("Bob");
// 적들 생성
auto orc = createEnemy("Orc", 15);
auto goblin = createEnemy("Goblin", 10);
// 아이템들 생성
auto potion = createItem("Potion", 50);
// 적들이 플레이어를 타겟으로 설정
orc->setTarget(alice);
goblin->setTarget(bob);
showStats();
// 게임 진행 (3턴)
for (int turn = 1; turn <= 3; turn++) {
cout << "\n🎯 === 턴 " << turn << " ===\n";
update();
// 2턴에서 Bob이 포션 사용
if (turn == 2) {
potion->use(bob);
}
showStats();
if (players.empty()) {
cout << "💀 모든 플레이어가 사망! 게임 오버!\n";
break;
}
}
cout << "\n🏁 시뮬레이션 종료\n";
}
};
int main() {
cout << "🎮 자동 메모리 관리 시스템 데모\n";
cout << "=".repeat(50) << "\n\n";
{
GameWorld world;
world.simulate();
cout << "\n💡 GameWorld가 스코프를 벗어납니다...\n";
}
cout << "\n🎯 모든 객체가 자동으로 정리되었습니다!\n";
cout << "✨ 스마트 포인터 덕분에 메모리 누수 없음!\n";
return 0;
}💡 핵심 정리
- unique_ptr: 독점 소유권, 이동만 가능 🎯
- shared_ptr: 공유 소유권, 참조 카운팅 👥
- weak_ptr: 순환 참조 방지, 약한 참조 🔄
- RAII: 생성자에서 획득, 소멸자에서 해제 🛡️
- make_unique/make_shared: 권장하는 생성 방법 ⚡
- move 시맨틱: 효율적인 소유권 이전 📦
- 스마트 포인터 = 안전 + 편의성! 🚀
✅ 실습 체크리스트
🚀 다음 시간 예고
다음 시간에는 Unit 14: C++ 고급 주제들을 시작해볼 예정이에요! 🎯
- 템플릿과 제네릭 프로그래밍
- 예외 처리와 오류 관리
- 멀티스레딩과 동시성
- 최적화 기법과 성능 향상
C++의 진정한 파워를 경험할 시간이 다가왔어요! 더 강력하고 전문적인 프로그래머가 되어보죠! 💪
Last updated on