Topic 4: 이동 생성자와 이동 대입 (C++11) 🚀
🎯 학습 목표
- 이동 의미론(Move Semantics)의 개념을 이해할 수 있다
- rvalue와 lvalue의 차이를 구분할 수 있다
- 이동 생성자와 이동 대입 연산자를 구현할 수 있다
- Rule of Five를 이해하고 적용할 수 있다
🏃 이사로 이해하는 이동 의미론
이동(Move)과 복사(Copy)의 차이를 이사로 이해해봅시다!
복사 vs 이동 🏠
-
복사 (Copy): 짐을 똑같이 하나 더 만들기
- 시간이 오래 걸림 ⏱️
- 자원을 두 배로 사용 💰
- 원본은 그대로 유지
-
이동 (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