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

Topic 4: 이동 생성자와 이동 대입 (C++11) 🚀

🎯 학습 목표

  • 이동 의미론(Move Semantics)의 개념을 이해할 수 있다
  • rvalue와 lvalue의 차이를 구분할 수 있다
  • 이동 생성자와 이동 대입 연산자를 구현할 수 있다
  • Rule of Five를 이해하고 적용할 수 있다

🏃 이사로 이해하는 이동 의미론

이동(Move)과 복사(Copy)의 차이를 이사로 이해해봅시다!

복사 vs 이동 🏠

  1. 복사 (Copy): 짐을 똑같이 하나 더 만들기

    • 시간이 오래 걸림 ⏱️
    • 자원을 두 배로 사용 💰
    • 원본은 그대로 유지
  2. 이동 (Move): 짐을 그대로 옮기기

    • 빠르고 효율적 ⚡
    • 자원 재활용 ♻️
    • 원본은 비어있게 됨
// 복사: 책을 복사기로 복사 Book copiedBook = originalBook; // 모든 페이지를 복사 // 이동: 책을 그냥 건네주기 Book movedBook = std::move(originalBook); // 책 자체를 전달

📝 lvalue와 rvalue 이해하기

간단한 구분법 🎯

  • lvalue: 이름이 있고, 주소를 가질 수 있는 값
  • rvalue: 임시값, 곧 사라질 값
int x = 10; // x는 lvalue, 10은 rvalue int y = x + 5; // y는 lvalue, (x+5)는 rvalue string s1 = "Hello"; // s1은 lvalue string s2 = s1 + " World"; // (s1 + " World")는 rvalue // & : lvalue 참조 // && : rvalue 참조 (C++11)

🎮 이동 생성자 구현하기

#include <iostream> #include <cstring> using namespace std; class BigData { private: char* data; size_t size; static int copyCount; // 복사 횟수 static int moveCount; // 이동 횟수 public: // 일반 생성자 BigData(size_t s) : size(s) { data = new char[size]; memset(data, 0, size); cout << "📦 BigData 생성 (크기: " << size << ")" << endl; } // 복사 생성자 - 깊은 복사 (느림) BigData(const BigData& other) : size(other.size) { data = new char[size]; memcpy(data, other.data, size); // 데이터 복사 copyCount++; cout << "📋 복사 생성자 호출 (복사 #" << copyCount << ")" << endl; } // 이동 생성자 - 자원 이동 (빠름!) ⭐ BigData(BigData&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; // 원본 포인터 무효화 other.size = 0; moveCount++; cout << "🏃 이동 생성자 호출 (이동 #" << moveCount << ")" << endl; } // 소멸자 ~BigData() { if (data) { delete[] data; cout << "🗑️ BigData 소멸" << endl; } else { cout << "🗑️ 빈 BigData 소멸" << endl; } } // 크기 반환 size_t getSize() const { return size; } // 통계 출력 static void showStats() { cout << "\n📊 통계: 복사 " << copyCount << "회, 이동 " << moveCount << "회" << endl; } }; int BigData::copyCount = 0; int BigData::moveCount = 0; // 팩토리 함수 - rvalue 반환 BigData createBigData(size_t size) { BigData temp(size); return temp; // 이동 생성자 호출됨! } int main() { cout << "=== 이동 생성자 테스트 ===" << endl; // 1. 일반적인 복사 cout << "\n--- 복사 생성자 ---" << endl; BigData data1(1000); BigData data2 = data1; // 복사 생성자 // 2. 이동 생성자 cout << "\n--- 이동 생성자 ---" << endl; BigData data3 = std::move(data1); // 이동 생성자 cout << "data1 크기: " << data1.getSize() << " (비어있음)" << endl; cout << "data3 크기: " << data3.getSize() << endl; // 3. 함수 반환값 (자동 이동) cout << "\n--- 함수 반환 (자동 이동) ---" << endl; BigData data4 = createBigData(2000); BigData::showStats(); return 0; }

🔄 이동 대입 연산자

#include <iostream> #include <string> #include <vector> using namespace std; class TextBuffer { private: string* content; size_t lineCount; string filename; public: // 생성자 TextBuffer(const string& fname) : filename(fname), lineCount(0) { content = new string[100]; // 최대 100줄 cout << "📄 TextBuffer 생성: " << filename << endl; } // 복사 생성자 TextBuffer(const TextBuffer& other) : filename(other.filename + "_copy"), lineCount(other.lineCount) { content = new string[100]; for (size_t i = 0; i < lineCount; i++) { content[i] = other.content[i]; } cout << "📋 복사 생성자: " << filename << endl; } // 이동 생성자 TextBuffer(TextBuffer&& other) noexcept : content(other.content), lineCount(other.lineCount), filename(std::move(other.filename)) { other.content = nullptr; other.lineCount = 0; cout << "🏃 이동 생성자: " << filename << endl; } // 복사 대입 연산자 TextBuffer& operator=(const TextBuffer& other) { cout << "= 복사 대입" << endl; if (this != &other) { delete[] content; filename = other.filename + "_assign"; lineCount = other.lineCount; content = new string[100]; for (size_t i = 0; i < lineCount; i++) { content[i] = other.content[i]; } } return *this; } // 이동 대입 연산자 ⭐ TextBuffer& operator=(TextBuffer&& other) noexcept { cout << "➡️ 이동 대입" << endl; if (this != &other) { delete[] content; content = other.content; lineCount = other.lineCount; filename = std::move(other.filename); other.content = nullptr; other.lineCount = 0; } return *this; } // 소멸자 ~TextBuffer() { delete[] content; cout << "🗑️ TextBuffer 소멸: " << filename << endl; } // 텍스트 추가 void addLine(const string& line) { if (lineCount < 100) { content[lineCount++] = line; cout << "✏️ 추가: " << line << endl; } } // 내용 표시 void show() const { cout << "\n=== " << filename << " ===" << endl; for (size_t i = 0; i < lineCount; i++) { cout << i+1 << ": " << content[i] << endl; } if (lineCount == 0) { cout << "(비어있음)" << endl; } } }; int main() { cout << "=== 이동 대입 연산자 테스트 ===" << endl; // 버퍼 생성 TextBuffer buffer1("file1.txt"); buffer1.addLine("첫 번째 줄"); buffer1.addLine("두 번째 줄"); TextBuffer buffer2("file2.txt"); buffer2.addLine("다른 내용"); // 복사 대입 cout << "\n--- 복사 대입 ---" << endl; TextBuffer buffer3("temp.txt"); buffer3 = buffer1; // 복사 대입 buffer3.show(); // 이동 대입 cout << "\n--- 이동 대입 ---" << endl; buffer2 = std::move(buffer1); // 이동 대입 buffer2.show(); buffer1.show(); // 비어있음 return 0; }

📐 Rule of Five (5의 규칙)

C++11부터는 5개의 특별한 멤버 함수를 관리해야 합니다:

class ModernClass { public: // 1. 소멸자 ~ModernClass(); // 2. 복사 생성자 ModernClass(const ModernClass& other); // 3. 복사 대입 연산자 ModernClass& operator=(const ModernClass& other); // 4. 이동 생성자 (C++11) ModernClass(ModernClass&& other) noexcept; // 5. 이동 대입 연산자 (C++11) ModernClass& operator=(ModernClass&& other) noexcept; };

🎮 실전 예제: 게임 리소스 전송

#include <iostream> #include <vector> #include <chrono> using namespace std; using namespace chrono; class GameAsset { private: vector<int>* data; string name; size_t size; public: // 생성자 GameAsset(const string& n, size_t s) : name(n), size(s) { data = new vector<int>(size, 1); cout << "🎨 에셋 생성: " << name << " (" << size << " elements)" << endl; } // Rule of Five 구현 // 1. 소멸자 ~GameAsset() { delete data; } // 2. 복사 생성자 GameAsset(const GameAsset& other) : name(other.name + "_copy"), size(other.size) { data = new vector<int>(*other.data); } // 3. 복사 대입 GameAsset& operator=(const GameAsset& other) { if (this != &other) { delete data; name = other.name + "_copy"; size = other.size; data = new vector<int>(*other.data); } return *this; } // 4. 이동 생성자 GameAsset(GameAsset&& other) noexcept : data(other.data), name(std::move(other.name)), size(other.size) { other.data = nullptr; other.size = 0; } // 5. 이동 대입 GameAsset& operator=(GameAsset&& other) noexcept { if (this != &other) { delete data; data = other.data; name = std::move(other.name); size = other.size; other.data = nullptr; other.size = 0; } return *this; } string getName() const { return name; } }; // 성능 비교 함수 void performanceComparison() { cout << "\n=== 성능 비교 ===" << endl; const size_t assetSize = 1000000; // 큰 데이터 // 복사 시간 측정 auto start = high_resolution_clock::now(); GameAsset original("texture", assetSize); GameAsset copied = original; // 복사 auto end = high_resolution_clock::now(); auto copyTime = duration_cast<milliseconds>(end - start).count(); // 이동 시간 측정 start = high_resolution_clock::now(); GameAsset source("model", assetSize); GameAsset moved = std::move(source); // 이동 end = high_resolution_clock::now(); auto moveTime = duration_cast<milliseconds>(end - start).count(); cout << "📋 복사 시간: " << copyTime << "ms" << endl; cout << "🏃 이동 시간: " << moveTime << "ms" << endl; cout << "⚡ 속도 향상: " << (copyTime > 0 ? copyTime / (moveTime + 1) : 0) << "배 빠름!" << endl; } // 컨테이너에서의 이동 void containerMoveExample() { cout << "\n=== 컨테이너 이동 예제 ===" << endl; vector<GameAsset> assets; assets.reserve(3); // 재할당 방지 // emplace_back - 직접 생성 (이동 없음) cout << "\n1. emplace_back (직접 생성):" << endl; assets.emplace_back("sword", 100); // push_back with move - 이동 cout << "\n2. push_back with move:" << endl; GameAsset shield("shield", 200); assets.push_back(std::move(shield)); // push_back with temporary - 자동 이동 cout << "\n3. push_back with temporary:" << endl; assets.push_back(GameAsset("potion", 50)); cout << "\n최종 에셋 목록:" << endl; for (const auto& asset : assets) { cout << " • " << asset.getName() << endl; } } int main() { cout << "=== 게임 리소스 이동 시스템 ===" << endl; performanceComparison(); containerMoveExample(); return 0; }

💡 이동을 사용해야 할 때

✅ 이동이 유용한 경우

  • 함수에서 객체 반환
  • 임시 객체 처리
  • 컨테이너에 객체 추가
  • 큰 데이터 전송

⚠️ 주의사항

  • 이동 후 원본은 유효하지만 비어있는 상태
  • noexcept 명시 권장 (성능 최적화)
  • 자가 대입 검사 필요

💡 핵심 정리

  • 이동 의미론: 자원을 복사하지 않고 이동
  • rvalue 참조 (&&): 임시 객체를 받는 참조
  • std::move: lvalue를 rvalue로 변환
  • Rule of Five: 5개의 특별 멤버 함수 관리
  • 성능 향상: 불필요한 복사 제거

✅ 실습 체크리스트

🎉 Unit 11 완료!

축하합니다! 생성자와 소멸자를 완벽히 마스터했습니다! 🎊

이번 Unit에서 배운 것들:

  • ✅ 다양한 생성자 형태
  • ✅ 복사 생성자와 깊은 복사
  • ✅ 소멸자와 RAII 패턴
  • ✅ 이동 의미론과 성능 최적화

다음 Unit 예고

Unit 12: 상속과 다형성

  • 클래스 상속
  • 가상 함수
  • 추상 클래스
  • 다형성 활용

“이동 의미론으로 더 빠른 프로그램을 만드세요! 🚀”

Last updated on