Skip to Content
💻 코리아IT아카데미 신촌 - 프로그래밍 학습 자료
C++ 프로그래밍Unit 11: 생성자와 소멸자Topic 3: 소멸자와 자원 관리

Topic 3: 소멸자와 자원 관리 🗑️

🎯 학습 목표

  • 소멸자의 역할과 호출 시점을 이해할 수 있다
  • 메모리 누수를 방지하는 방법을 알 수 있다
  • RAII 패턴을 이해하고 활용할 수 있다
  • 자원 관리의 중요성을 설명할 수 있다

🏠 이사 나가기로 이해하는 소멸자

소멸자는 집에서 이사 나갈 때 하는 일과 같습니다!

이사 나갈 때 꼭 해야 할 일들 📦

  1. 🔌 전기/가스 끄기: 열려있는 파일 닫기
  2. 🗑️ 쓰레기 버리기: 동적 메모리 해제
  3. 🔑 열쇠 반납: 시스템 자원 반환
  4. 🧹 청소하기: 임시 데이터 정리
class Tenant { public: ~Tenant() { turnOffElectricity(); // 전기 끄기 throwAwayTrash(); // 쓰레기 처리 returnKeys(); // 열쇠 반납 cout << "깔끔하게 이사 완료! 👋" << endl; } };

💭 소멸자란?

**소멸자(Destructor)**는 객체가 소멸될 때 자동으로 호출되는 특별한 함수입니다!

소멸자의 특징 📝

class MyClass { public: // 소멸자의 특징 ~MyClass() { // 1. 물결표(~) + 클래스명 // 2. 매개변수 없음 // 3. 반환 타입 없음 // 4. 오버로딩 불가 (하나만!) cout << "객체가 소멸됩니다!" << endl; } };

🚨 메모리 누수의 위험

메모리 누수 시연

#include <iostream> using namespace std; // 문제가 있는 클래스 - 메모리 누수 발생! ❌ class LeakyClass { private: int* data; int size; public: LeakyClass(int s) : size(s) { data = new int[size]; cout << "📦 " << size << "개 int 메모리 할당" << endl; } // 소멸자가 없음! - 메모리 누수! }; // 올바른 클래스 - 메모리 누수 방지! ✅ class SafeClass { private: int* data; int size; public: SafeClass(int s) : size(s) { data = new int[size]; cout << "📦 " << size << "개 int 메모리 할당" << endl; } ~SafeClass() { delete[] data; // 메모리 해제! cout << "🗑️ 메모리 해제 완료" << endl; } }; void demonstrateMemoryLeak() { cout << "=== 메모리 누수 테스트 ===" << endl; for (int i = 0; i < 3; i++) { LeakyClass leak(1000); // 메모리 누수! // 함수 끝나면 data가 가리키던 메모리는 그대로 남음 } cout << "\n=== 안전한 메모리 관리 ===" << endl; for (int i = 0; i < 3; i++) { SafeClass safe(1000); // 자동으로 정리됨! // 소멸자가 메모리를 해제 } }

📁 실습: 파일 관리 클래스

#include <iostream> #include <fstream> #include <string> using namespace std; class FileHandler { private: string filename; ofstream* file; bool isOpen; int writeCount; public: // 생성자 - 파일 열기 FileHandler(const string& fname) : filename(fname), writeCount(0) { file = new ofstream(filename); if (file->is_open()) { isOpen = true; cout << "📂 파일 열기: " << filename << endl; *file << "=== 로그 시작 ===" << endl; } else { isOpen = false; cout << "❌ 파일 열기 실패: " << filename << endl; } } // 소멸자 - 자동으로 파일 닫기 ~FileHandler() { if (isOpen && file) { *file << "=== 로그 종료 ===" << endl; *file << "총 " << writeCount << "개 항목 기록됨" << endl; file->close(); cout << "📁 파일 닫기: " << filename << endl; cout << " ✅ " << writeCount << "개 항목 저장 완료" << endl; } delete file; } // 파일에 쓰기 void writeLine(const string& text) { if (isOpen && file) { *file << "[" << ++writeCount << "] " << text << endl; cout << "✏️ 기록: " << text << endl; } } // 중요 로그 쓰기 void writeImportant(const string& text) { if (isOpen && file) { *file << "[" << ++writeCount << "] ⭐ 중요: " << text << endl; cout << "⭐ 중요 기록: " << text << endl; } } }; // 자동 자원 관리 테스트 void testFileHandler() { cout << "=== 파일 핸들러 테스트 ===" << endl; { // 스코프 시작 FileHandler log("game_log.txt"); log.writeLine("게임 시작"); log.writeLine("플레이어 로그인"); log.writeImportant("보스 처치!"); log.writeLine("아이템 획득"); // 스코프 끝 - 자동으로 파일 닫힘! } cout << "\n파일이 자동으로 닫혔습니다!" << endl; }

🛡️ RAII 패턴 (Resource Acquisition Is Initialization)

RAII는 C++의 핵심 패턴으로, 자원 획득은 초기화라는 의미입니다!

RAII의 원칙 📋

  1. 생성자에서 자원 획득
  2. 소멸자에서 자원 해제
  3. 예외 안전성 보장
#include <iostream> #include <memory> using namespace std; // RAII를 적용한 스마트 배열 클래스 template<typename T> class SmartArray { private: T* data; size_t size; public: // 생성자 - 자원 획득 SmartArray(size_t s) : size(s) { data = new T[size]; cout << "🔨 SmartArray 생성 (크기: " << size << ")" << endl; } // 소멸자 - 자원 해제 ~SmartArray() { delete[] data; cout << "🗑️ SmartArray 소멸 (메모리 자동 해제)" << endl; } // 복사 생성자 (깊은 복사) SmartArray(const SmartArray& other) : size(other.size) { data = new T[size]; for (size_t i = 0; i < size; i++) { data[i] = other.data[i]; } cout << "📋 SmartArray 복사" << endl; } // 인덱스 연산자 T& operator[](size_t index) { if (index >= size) { throw out_of_range("인덱스 범위 초과!"); } return data[index]; } size_t getSize() const { return size; } }; void testSmartArray() { cout << "=== SmartArray 테스트 ===" << endl; try { SmartArray<int> arr(5); // 배열 사용 for (int i = 0; i < 5; i++) { arr[i] = i * 10; } // 출력 cout << "배열 내용: "; for (int i = 0; i < 5; i++) { cout << arr[i] << " "; } cout << endl; // 예외 발생 시에도 자동으로 메모리 해제! // arr[10] = 100; // 예외 발생 } catch (const exception& e) { cout << "예외 발생: " << e.what() << endl; } cout << "함수 종료 - 메모리 자동 정리!" << endl; }

🎮 실전 예제: 게임 리소스 매니저

#include <iostream> #include <vector> #include <string> using namespace std; // 게임 리소스 타입 enum ResourceType { TEXTURE, SOUND, MODEL }; // 리소스 클래스 class GameResource { private: string name; ResourceType type; void* data; size_t size; public: GameResource(const string& n, ResourceType t, size_t s) : name(n), type(t), size(s) { data = malloc(size); // 실제로는 파일 로드 등 string typeStr = (type == TEXTURE) ? "텍스처" : (type == SOUND) ? "사운드" : "3D 모델"; cout << "📦 " << typeStr << " 로드: " << name << " (" << size << " bytes)" << endl; } ~GameResource() { free(data); string typeStr = (type == TEXTURE) ? "텍스처" : (type == SOUND) ? "사운드" : "3D 모델"; cout << "🗑️ " << typeStr << " 해제: " << name << endl; } string getName() const { return name; } size_t getSize() const { return size; } }; // 리소스 매니저 클래스 class ResourceManager { private: vector<GameResource*> resources; size_t totalMemory; size_t maxMemory; public: ResourceManager(size_t maxMem = 1000000) : totalMemory(0), maxMemory(maxMem) { cout << "🎮 리소스 매니저 초기화" << endl; cout << " 최대 메모리: " << maxMemory << " bytes" << endl; } ~ResourceManager() { cout << "\n🔄 리소스 매니저 종료 중..." << endl; // 모든 리소스 자동 해제 for (auto* resource : resources) { delete resource; } resources.clear(); cout << "✅ 모든 리소스 정리 완료!" << endl; } bool loadResource(const string& name, ResourceType type, size_t size) { if (totalMemory + size > maxMemory) { cout << "❌ 메모리 부족! (" << name << ")" << endl; return false; } GameResource* resource = new GameResource(name, type, size); resources.push_back(resource); totalMemory += size; cout << " 메모리 사용량: " << totalMemory << "/" << maxMemory << " (" << (totalMemory * 100 / maxMemory) << "%)" << endl; return true; } void showStatus() const { cout << "\n=== 리소스 상태 ===" << endl; cout << "로드된 리소스: " << resources.size() << "개" << endl; cout << "메모리 사용: " << totalMemory << "/" << maxMemory << " bytes" << endl; if (!resources.empty()) { cout << "\n리소스 목록:" << endl; for (const auto* res : resources) { cout << " • " << res->getName() << " (" << res->getSize() << " bytes)" << endl; } } } }; int main() { cout << "=== 게임 리소스 관리 시스템 ===" << endl; { // 스코프 시작 ResourceManager manager(500000); // 500KB 제한 // 리소스 로드 manager.loadResource("player.png", TEXTURE, 50000); manager.loadResource("enemy.png", TEXTURE, 45000); manager.loadResource("background.jpg", TEXTURE, 120000); manager.loadResource("shoot.wav", SOUND, 30000); manager.loadResource("bgm.mp3", SOUND, 180000); manager.loadResource("sword.obj", MODEL, 65000); // 메모리 초과 테스트 manager.loadResource("huge_texture.png", TEXTURE, 100000); manager.showStatus(); cout << "\n게임 종료 중..." << endl; } // 스코프 끝 - 모든 리소스 자동 해제! cout << "\n✅ 프로그램 종료 - 메모리 누수 없음!" << endl; return 0; }

📊 소멸자 호출 순서

class Component { string name; public: Component(string n) : name(n) { cout << " 📦 Component 생성: " << name << endl; } ~Component() { cout << " 🗑️ Component 소멸: " << name << endl; } }; class GameObject { string name; Component comp1; Component comp2; public: GameObject(string n) : name(n), comp1("그래픽"), comp2("물리") { cout << "🎮 GameObject 생성: " << name << endl; } ~GameObject() { cout << "🎮 GameObject 소멸: " << name << endl; // 멤버 변수들은 역순으로 소멸됨! } };

💡 핵심 정리

  • 소멸자: 객체 소멸 시 자동 호출되는 정리 함수
  • 메모리 누수: 동적 할당된 메모리를 해제하지 않는 문제
  • RAII: 생성자에서 획득, 소멸자에서 해제
  • 자동 자원 관리: 스코프를 벗어나면 자동으로 정리
  • 호출 순서: 생성의 역순으로 소멸

✅ 실습 체크리스트

🚀 다음 시간 예고

다음 시간에는 이동 생성자와 이동 대입에 대해 알아볼 거예요!

  • 이동 의미론 (Move Semantics)
  • rvalue 참조
  • 성능 최적화

“소멸자로 깔끔한 자원 관리를 실현하세요! 🧹”

Last updated on