[2026] C++ 복사/이동 생성자 | Rule of Five 가이드
이 글의 핵심
C++ 복사/이동 생성자 - Rule of Five 가이드. C++ 복사/이동 생성자의 Rule of Five, 복사 생성자, 이동 생성자를 실전 코드와 함께 설명합니다.
Rule of Five
리소스를 관리하는 클래스는 5개 특수 멤버 함수 정의 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Resource {
private:
int* data;
size_t size;
public:
// 1. 생성자
Resource(size_t s) : size(s), data(new int[s]) {}
// 2. 소멸자
~Resource() {
delete[] data;
}
// 3. 복사 생성자
Resource(const Resource& other)
: size(other.size), data(new int[other.size]) {
copy(other.data, other.data + size, data);
}
// 4. 복사 대입 연산자
Resource& operator=(const Resource& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
copy(other.data, other.data + size, data);
}
return *this;
}
// 5. 이동 생성자
Resource(Resource&& other) noexcept
: size(other.size), data(other.data) {
other.data = nullptr;
other.size = 0;
}
// 6. 이동 대입 연산자
Resource& operator=(Resource&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};
복사 생성자
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 타입 정의
class String {
private:
char* data;
size_t length;
public:
String(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// 복사 생성자
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
cout << "복사 생성자 호출" << endl;
}
~String() {
delete[] data;
}
};
int main() {
String s1("Hello");
String s2 = s1; // 복사 생성자 호출
String s3(s1); // 복사 생성자 호출
}
이동 생성자
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class String {
private:
char* data;
size_t length;
public:
String(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
// 이동 생성자
String(String&& other) noexcept {
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
cout << "이동 생성자 호출" << endl;
}
~String() {
delete[] data;
}
};
int main() {
String s1("Hello");
String s2 = move(s1); // 이동 생성자 호출
// s1은 이제 비어있음
}
실전 예시
예시 1: 동적 배열
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
class DynamicArray {
private:
T* data;
size_t size;
size_t capacity;
public:
DynamicArray(size_t cap = 10)
: size(0), capacity(cap), data(new T[cap]) {}
~DynamicArray() {
delete[] data;
}
// 복사 생성자
DynamicArray(const DynamicArray& other)
: size(other.size), capacity(other.capacity),
data(new T[other.capacity]) {
copy(other.data, other.data + size, data);
}
// 복사 대입
DynamicArray& operator=(const DynamicArray& other) {
if (this != &other) {
delete[] data;
size = other.size;
capacity = other.capacity;
data = new T[capacity];
copy(other.data, other.data + size, data);
}
return *this;
}
// 이동 생성자
DynamicArray(DynamicArray&& other) noexcept
: size(other.size), capacity(other.capacity), data(other.data) {
other.data = nullptr;
other.size = 0;
other.capacity = 0;
}
// 이동 대입
DynamicArray& operator=(DynamicArray&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
capacity = other.capacity;
other.data = nullptr;
other.size = 0;
other.capacity = 0;
}
return *this;
}
void push_back(const T& value) {
if (size == capacity) {
resize();
}
data[size++] = value;
}
T& operator {
return data[index];
}
size_t getSize() const {
return size;
}
private:
void resize() {
capacity *= 2;
T* newData = new T[capacity];
copy(data, data + size, newData);
delete[] data;
data = newData;
}
};
int main() {
DynamicArray<int> arr1;
arr1.push_back(1);
arr1.push_back(2);
DynamicArray<int> arr2 = arr1; // 복사
DynamicArray<int> arr3 = move(arr1); // 이동
}
예시 2: 파일 핸들러
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class FileHandle {
private:
FILE* file;
string filename;
public:
FileHandle(const string& name) : filename(name) {
file = fopen(name.c_str(), "r");
if (!file) {
throw runtime_error("파일 열기 실패");
}
}
~FileHandle() {
if (file) {
fclose(file);
}
}
// 복사 금지
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// 이동 허용
FileHandle(FileHandle&& other) noexcept
: file(other.file), filename(move(other.filename)) {
other.file = nullptr;
}
FileHandle& operator=(FileHandle&& other) noexcept {
if (this != &other) {
if (file) {
fclose(file);
}
file = other.file;
filename = move(other.filename);
other.file = nullptr;
}
return *this;
}
string read() {
if (!file) return "";
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
string content(size, '\0');
fread(&content[0], 1, size, file);
return content;
}
};
int main() {
FileHandle fh1("test.txt");
// FileHandle fh2 = fh1; // 에러: 복사 금지
FileHandle fh2 = move(fh1); // OK: 이동
}
예시 3: 스마트 포인터 (간단한 버전)
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
class UniquePtr {
private:
T* ptr;
public:
explicit UniquePtr(T* p = nullptr) : ptr(p) {}
~UniquePtr() {
delete ptr;
}
// 복사 금지
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;
// 이동 허용
UniquePtr(UniquePtr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}
UniquePtr& operator=(UniquePtr&& other) noexcept {
if (this != &other) {
delete ptr;
ptr = other.ptr;
other.ptr = nullptr;
}
return *this;
}
T& operator*() const {
return *ptr;
}
T* operator->() const {
return ptr;
}
T* get() const {
return ptr;
}
T* release() {
T* temp = ptr;
ptr = nullptr;
return temp;
}
};
int main() {
UniquePtr<int> p1(new int(42));
// UniquePtr<int> p2 = p1; // 에러: 복사 금지
UniquePtr<int> p2 = move(p1); // OK: 이동
cout << *p2 << endl; // 42
}
복사 생략 (Copy Elision)
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
String createString() {
return String("Hello"); // 복사/이동 생략 가능
}
int main() {
String s = createString(); // 복사/이동 없음 (C++17)
}
자주 발생하는 문제
문제 1: 얕은 복사
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 얕은 복사 (기본 복사 생성자)
class BadString {
private:
char* data;
public:
BadString(const char* str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
~BadString() {
delete[] data;
}
// 기본 복사 생성자: 포인터만 복사
};
BadString s1("Hello");
BadString s2 = s1; // 같은 메모리 가리킴
// 소멸 시 double free!
// ✅ 깊은 복사
class GoodString {
private:
char* data;
public:
GoodString(const char* str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
GoodString(const GoodString& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
~GoodString() {
delete[] data;
}
};
문제 2: 자기 대입
다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 자기 대입 미처리
Resource& operator=(const Resource& other) {
delete[] data; // 자기 대입 시 문제!
size = other.size;
data = new int[size];
copy(other.data, other.data + size, data);
return *this;
}
// ✅ 자기 대입 체크
Resource& operator=(const Resource& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
copy(other.data, other.data + size, data);
}
return *this;
}
문제 3: noexcept 누락
아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ noexcept 없음
Resource(Resource&& other) {
// ...
}
// ✅ noexcept 추가
Resource(Resource&& other) noexcept {
// ...
}
// 이유: vector 등에서 이동 생성자가 noexcept여야 사용
FAQ
Q1: Rule of Five는 언제 필요?
A: 리소스(메모리, 파일 등)를 직접 관리할 때.
Q2: 복사 vs 이동?
A:
- 복사: 독립적인 복사본
- 이동: 소유권 이전, 빠름
Q3: = delete는?
A: 복사/이동 금지. unique_ptr 같은 경우.
Q4: noexcept는 왜?
A: 예외 안전성, 성능 최적화 (vector 등).
Q5: Rule of Zero는?
A: 스마트 포인터 사용 시 특수 멤버 함수 불필요.
Q6: 복사/이동 생성자 학습 리소스는?
A:
- “Effective Modern C++”
- cppreference.com
- “C++ Primer”
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Algorithm Copy | “복사 알고리즘” 가이드
- C++ default와 delete | “특수 멤버 함수” 가이드
- C++ Custom Deleters | “커스텀 삭제자” 가이드