복사 생성자
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 20번째.
복사 생성자(copy constructor)는 기존 객체의 복사본으로 새로운 객체를 생성합니다. 이는 동일한 타입의 다른 객체로부터 객체를 초기화하거나, 객체를 값에 의해 전달하거나, 객체를 값에 의해 반환할 때 호출됩니다.
복사 생성자는 동일한 클래스의 객체에 대한 const 참조를 인자로 받습니다:
class Player {
std::string name;
int health;
public:
Player(std::string n, int h) : name(n), health(h) {}
// Copy constructor
Player(const Player& other) {
name = other.name;
health = other.health;
}
};
Player original("Hero", 100);
Player copy = original; // Copy constructor called
Player another(original); // Also calls copy constructor복사 생성자를 정의하지 않으면, 컴파일러는 얕은 복사(shallow copy)를 수행하는 생성자를 생성합니다. 이는 각 멤버의 값을 직접 복사합니다. 이 방식은 단순 타입의 경우에는 잘 작동하지만, 클래스가 동적 메모리를 관리할 때는 문제를 일으킵니다.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
// 깊은 복사 생성자
Buffer(const Buffer& other) : size(other.size) {
data = new int[size]; // 새로운 메모리 할당
for (size_t i = 0; i < size; i++)
data[i] = other.data[i]; // 내용 복사
}
~Buffer() { delete[] data; }
};사용자 정의 복사 생성자가 없으면, 두 객체는 동일한 메모리를 가리키게 됩니다. 하나가 소멸되면, 다른 하나는 댕글링 포인터(dangling pointer)를 갖게 됩니다. 깊은 복사(deep copy)는 새로운 메모리를 할당하고 실제 데이터를 복사하여, 각 객체가 고유한 리소스를 소유하도록 보장합니다.
챌린지
쉬움클래스가 동적 메모리를 관리할 때 깊은 복사(deep copying)의 중요성을 보여주는 메시지 기록 시스템을 구축해 보겠습니다. 동적으로 할당된 배열에 메시지를 저장하는 MessageLog 클래스를 만들고, 각 복사본이 자신만의 독립적인 메모리를 갖도록 적절한 복사 생성자를 구현합니다.
코드를 구성하기 위해 두 개의 파일을 생성합니다:
MessageLog.h: 동적 메모리를 사용하여 메시지 컬렉션을 관리하는MessageLog클래스를 정의합니다. 클래스에는 다음이 포함되어야 합니다:- Private 멤버: 메시지 저장을 위한 문자열 배열 포인터, 최대 메시지 수를 위한
capacity, 현재 메시지 수를 위한count - 용량(capacity)을 인자로 받아 힙(heap)에 배열을 할당하고 count를 0으로 초기화하는 매개변수화된 생성자
- 깊은 복사(deep copy)를 수행하는 복사 생성자 — 새로운 메모리를 할당하고 원본 객체의 모든 메시지를 복사합니다.
- 할당된 메모리를 해제하고
"MessageLog destroyed"를 출력하는 소멸자 - 공간이 있는 경우 메시지를 추가하는
addMessage(string msg)메서드 - 현재 메시지 개수를 반환하는
getCount()메서드 - 주어진 인덱스의 메시지를 반환하는
getMessage(int index)메서드
- Private 멤버: 메시지 저장을 위한 문자열 배열 포인터, 최대 메시지 수를 위한
main.cpp: 복사 생성자가 독립적인 복사본을 생성함을 증명합니다. 입력에서 용량 값과 두 개의 메시지를 읽은 후 다음을 수행합니다:- 입력받은 용량으로
original이라는 이름의MessageLog를 생성합니다. original에 두 메시지를 모두 추가합니다.- 복사 생성자를 사용하여
backup이라는 이름의 복사본을 생성합니다. original에만 세 번째 메시지인"New message"를 추가합니다."Original count: <count>"를 출력합니다."Backup count: <count>"를 출력합니다.backup의 모든 메시지를 한 줄에 하나씩 출력합니다.
- 입력받은 용량으로
입력 형식은 다음과 같습니다:
- 첫 번째 줄: 용량 (정수)
- 두 번째 줄: 첫 번째 메시지 (문자열)
- 세 번째 줄: 두 번째 메시지 (문자열)
깊은 복사가 올바르게 구현되었다면, original은 3개의 메시지를 가지는 반면 backup은 2개의 메시지를 유지해야 합니다. 이는 두 객체가 독립적인 메모리를 가지고 있음을 증명합니다. 적절한 복사 생성자가 없다면 두 객체는 동일한 배열을 공유하게 되어 한쪽의 변경이 다른 쪽에 영향을 미치게 됩니다!
main.cpp에서 #include "MessageLog.h"를 사용하여 헤더 파일을 포함하세요.
치트 시트
복사 생성자(copy constructor)는 기존 객체의 복사본으로 새로운 객체를 생성합니다. 이는 동일한 클래스의 객체에 대한 const 참조를 인자로 받습니다:
class Player {
std::string name;
int health;
public:
Player(std::string n, int h) : name(n), health(h) {}
// 복사 생성자
Player(const Player& other) {
name = other.name;
health = other.health;
}
};
Player original("Hero", 100);
Player copy = original; // 복사 생성자 호출
Player another(original); // 역시 복사 생성자 호출복사 생성자를 정의하지 않으면, 컴파일러는 각 멤버의 값을 직접 복사하는 얕은 복사(shallow copy)를 수행하는 생성자를 생성합니다. 이는 클래스가 동적 메모리를 관리할 때 문제를 일으킵니다.
깊은 복사 생성자(deep copy constructor)는 새로운 메모리를 할당하고 실제 데이터를 복사하여, 각 객체가 자신만의 리소스를 소유하도록 보장합니다:
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
// 깊은 복사 생성자
Buffer(const Buffer& other) : size(other.size) {
data = new int[size]; // 새로운 메모리 할당
for (size_t i = 0; i < size; i++)
data[i] = other.data[i]; // 내용 복사
}
~Buffer() { delete[] data; }
};사용자 정의 복사 생성자가 없으면 두 객체 모두 동일한 메모리를 가리키게 되어, 하나가 소멸될 때 댕글링 포인터(dangling pointer)가 발생하게 됩니다.
직접 해보기
#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;
int main() {
// 입력 읽기
int capacity;
cin >> capacity;
cin.ignore();
string msg1, msg2;
getline(cin, msg1);
getline(cin, msg2);
// TODO: 입력받은 capacity로 'original'이라는 이름의 MessageLog 생성
// TODO: 두 메시지를 original에 추가
// TODO: 복사 생성자를 사용하여 'backup'이라는 이름의 복사본 생성
// TODO: original에만 "New message" 추가
// TODO: "Original count: <count>" 출력
// TODO: "Backup count: <count>" 출력
// TODO: backup의 모든 메시지를 한 줄에 하나씩 출력
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.