복사 생성자
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 104개 중 20번째.
복사 생성자는 기존 객체의 복사본으로 새 객체를 만듭니다. 같은 유형의 다른 객체로 객체를 초기화하거나, 객체를 값으로 전달하거나, 객체를 값으로 반환할 때 호출됩니다.
복사 생성자는 같은 클래스의 객체에 대한 const 참조를 받습니다. 반드시 참조여야 합니다. other를 값으로 전달하면 그 자체로 복사해야 하므로 복사 생성자가 계속해서 끝없이 호출됩니다. 복사본을 만든다고 해서 원본이 바뀌어서는 안 되므로 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); // 복사 생성자도 호출함복사 생성자를 정의하지 않으면 컴파일러가 각 멤버의 값을 그대로 복사하는 얕은 복사를 수행하는 복사 생성자를 생성합니다. 단순한 타입에서는 잘 작동하지만, 클래스가 동적 메모리를 관리할 때는 문제가 발생합니다.
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; }
};사용자 지정 복사 생성자가 없으면 두 객체가 같은 메모리를 가리키게 됩니다. 한 객체가 소멸되면 다른 객체에는 댕글링 포인터가 남게 됩니다. 깊은 복사는 새 메모리를 할당하고 실제 데이터를 복사하여 각 객체가 자체 리소스를 소유하도록 합니다.
챌린지
쉬움클래스가 동적 메모리를 관리할 때 깊은 복사가 왜 중요한지 보여 주는 메시지 기록 시스템을 만들어 봅시다. 메시지를 동적으로 할당한 배열에 저장하는 MessageLog 클래스를 만들고, 각 복사본이 독립적인 메모리를 갖도록 적절한 복사 생성자를 구현합니다.
코드를 정리하기 위해 파일 두 개를 만듭니다.
MessageLog.h: 동적 메모리를 사용해 메시지 모음을 관리하는MessageLog클래스를 정의합니다. 클래스에는 다음이 있어야 합니다.- 비공개 멤버: 메시지를 저장하기 위한 문자열 배열 포인터, 최대 메시지 수를 나타내는
capacity, 현재 메시지 수를 나타내는count - capacity를 인수로 받아 힙에 배열을 할당하고 count를 0으로 초기화하는 매개변수가 있는 생성자
- 새 메모리를 할당하고 원본 객체의 모든 메시지를 복사하는 깊은 복사를 수행하는 복사 생성자
- 할당된 메모리를 해제하고
"MessageLog destroyed"를 출력하는 소멸자 - 공간이 있으면 메시지를 추가하는
addMessage(string msg)메서드 - 현재 메시지 수를 반환하는
getCount()메서드 - 지정한 인덱스의 메시지를 반환하는
getMessage(int index)메서드
- 비공개 멤버: 메시지를 저장하기 위한 문자열 배열 포인터, 최대 메시지 수를 나타내는
main.cpp: 복사 생성자가 독립적인 복사본을 만드는 것을 보여 줍니다. 입력에서 capacity 값과 메시지 두 개를 읽은 다음 다음을 수행합니다.- 입력된 capacity로
original이라는MessageLog를 만듭니다. - 메시지 두 개를 모두
original에 추가합니다. - 복사 생성자를 사용해
backup이라는 복사본을 만듭니다. "New message"라는 세 번째 메시지를original에만 추가합니다."Original count: <count>"를 출력합니다."Backup count: <count>"를 출력합니다.backup의 모든 메시지를 한 줄에 하나씩 출력합니다.
- 입력된 capacity로
입력 형식은 다음과 같습니다.
- 첫 번째 줄: capacity(정수)
- 두 번째 줄: 첫 번째 메시지(문자열)
- 세 번째 줄: 두 번째 메시지(문자열)
깊은 복사가 올바르게 구현되면 original에는 메시지가 3개, backup에는 메시지가 2개 있어야 합니다. 이는 두 객체가 독립적인 메모리를 사용한다는 것을 보여 줍니다. 적절한 복사 생성자가 없으면 두 객체가 같은 배열을 공유하여 한쪽의 변경 사항이 다른 쪽에도 영향을 줍니다!
#include "MessageLog.h"를 사용해 main.cpp에 헤더 파일을 포함하세요.
직접 해보기
#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: 입력받은 용량으로 'original'이라는 MessageLog 만들기
// TODO: original에 두 메시지 모두 추가하기
// TODO: 복사 생성자를 사용해 'backup'이라는 복사본 만들기
// TODO: original에만 "New message" 추가하기
// TODO: "Original count: <count>" 출력하기
// TODO: "Backup count: <count>" 출력하기
// TODO: backup의 모든 메시지를 한 줄에 하나씩 출력하기
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
직접 연습해 보세요: 온라인 C++ 컴파일러