Skip to Content
💻 코리아IT아카데미 신촌 - 프로그래밍 학습 자료
C++ 프로그래밍Unit 13: 템플릿과 STLTopic 4: 스마트 포인터와 현대적 C++

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