|5.5| 복사 생성자
● 얕은 복사와 깊은 복사 (shallow copy & deep copy)
● 복사 생성 및 복사 생성자
복사 생성자 선언
1 2 3 4 | class ClassName { ClassName(ClassName& c); // 복사 생성자 }; | cs |
※ 복사 생성자의 매개변수는 오직 하나이며,
자기 클래스에 대한 참조로 선언된다.
또한, 복사 생성자는 클래스에 오직 한 개만 선언이 가능하다.
복사 생성자 실행
1 2 3 4 | Circle src(30); // 일반적인 생성자 호출 Circle dest(src); /* src 객체를 복사하여 dest 객체 생성 복사 생성자 Circle(Circle& c) 호출 */ | cs |
※ 예시 : Circle 크래스의 복사 생성자와 객체 복사
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include <iostream> using namespace std; class Circle { private: int radius; public: Circle(Circle& c); // 복사 생성자 선언 Circle() { radius = 1; } Circle(int radius) { this->radius = radius; } double getArea() { return 3.14*radius*radius; } }; Circle::Circle(Circle& c) { // 복사 생성자 구현 this->radius = c.radius; cout << "복사 생성자 실행 radius = " << radius << endl; } int main() { Circle src(30); // src 객체의 보통 생성자 호출 Circle dest(src); // dest 객체의 복사 생성자 호출 cout << "원본의 면적 = " << src.getArea() << endl; cout << "사본의 면적 = " << dest.getArea() << endl; } | cs |
실행 결과 :
복사 생성자 실행 radius = 30
원본의 면적 = 2826
사본의 면적 = 2826
※ 만약 복사 생성자를 따로 정의하지 않는다면?
=> 컴파일러가 디폴트 복사 생성자(default copy constructor)를 묵시적으로 삽입한다.
※ 하지만 default copy constructor 코드는 shallow copy를 실행하도록 만들어진 코드이다.
포인터 타입 변수가 없는 클래스의 경우, 얕은 복사는 전혀 문제가 없다.
" 공유 " 의 문제가 발생하지 않기 때문이다.
※ 예제 : 얕은 복사 생성자를 사용하여 문제가 생기는 경우 ( 프로그램이 비정상 종료 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #define _CRT_SECURE_NO_WARNINGS //비주얼 스튜디오에서 strcpy로 인한 오류를 막기 위한 선언문 #include <iostream> #include <cstring> using namespace std; class Person { // Person 클래스 선언 char* name; int id; public: Person(int id, const char* name); // 생성자 ~Person(); // 소멸자 void changeName(const char *name); void show() { cout << id << ',' << name << endl; } }; Person::Person(int id,const char* name) { // 생성자 this->id = id; int len = strlen(name); // name의 문자 개수 this->name = new char [len+1]; // name 문자열 공간 핟당 strcpy(this->name, name); // name에 문자열 복사 } Person::~Person() {// 소멸자 if(name) // 만일 name에 동적 할당된 배열이 있으면 delete [] name; // 동적 할당 메모리 소멸 } void Person::changeName(const char* name) { // 이름 변경 if(strlen(name) > strlen(this->name)) return; // 현재 name에 할당된 메모리보다 긴 이름으로 바꿀 수 없다. strcpy(this->name, name); } int main() { Person father(1, "Kitae"); // (1) father 객체 생성 Person daughter(father); // (2) daughter 객체 복사 생성. 복사 생성자 호출 cout << "daughter 객체 생성 직후 ----" << endl; father.show(); // (3) father 객체 출력 daughter.show(); // (3) daughter 객체 출력 daughter.changeName("Grace"); // (4) daughter의 이름을 "Grace"로 변경 cout << "daughter 이름을 Grace로 변경한 후 ----" << endl; father.show(); // (5) father 객체 출력 daughter.show(); // (5) daughter 객체 출력 return 0; // (6), (7) daughter, father 객체 소멸 } | cs |
※ main 함수 종료 시, daughter 객체가 먼저 소멸되고, father 객체의 소멸자가 name에 할당된 메모리를 힙에 반환하려고 할 때!!!
이미 반환한 메모리를 반환하게 되므로, 오류가 발생하고 프로그램이 비정상 종료된다.
※ 예시 : 깊은 복사 생성자를 가진 정상적인 Person 클래스
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | #define _CRT_SECURE_NO_WARNINGS //비주얼 스튜디오에서 strcpy로 인한 오류를 막기 위한 선언문 #include <iostream> #include <cstring> using namespace std; class Person { // Person 클래스 선언 char* name; int id; public: Person(int id, const char* name); // 생성자 Person(Person& person); // 복사 생성자 ~Person(); // 소멸자 void changeName(const char *name); void show() { cout << id << ',' << name << endl; } }; Person::Person(int id,const char* name) { // 생성자 this->id = id; int len = strlen(name); // name의 문자 개수 this->name = new char [len+1]; // name 문자열 공간 핟당 strcpy(this->name, name); // name에 문자열 복사 } Person::Person(Person& person) { // 복사 생성자 this->id = person.id; // id 값 복사 int len = strlen(person.name);// name의 문자 개수 this->name = new char [len+1]; // name을 위한 공간 핟당 strcpy(this->name, person.name); // name의 문자열 복사 cout << "복사 생성자 실행. 원본 객체의 이름 " << this->name << endl; } Person::~Person() {// 소멸자 if(name) // 만일 name에 동적 할당된 배열이 있으면 delete [] name; // 동적 할당 메모리 소멸 } void Person::changeName(const char* name) { // 이름 변경 if(strlen(name) > strlen(this->name)) return; // 현재 name에 할당된 메모리보다 긴 이름으로 바꿀 수 없다. strcpy(this->name, name); } int main() { Person father(1, "Kitae"); // (1) father 객체 생성 Person daughter(father); // (2) daughter 객체 복사 생성. 복사 생성자 호출 cout << "daughter 객체 생성 직후 ----" << endl; father.show(); // (3) father 객체 출력 daughter.show(); // (3) daughter 객체 출력 daughter.changeName("Grace"); // (4) // daughter의 이름을 "Grace"로 변경 cout << "daughter 이름을 Grace로 변경한 후 ----" << endl; father.show(); // (5) father 객체 출력 daughter.show(); // (5) daughter 객체 출력 return 0; // (6), (7) daughter, father 객체 소멸 } | cs |
● 묵시적 복사 생성 주의 상황들
1 | Person daughter(father); // 복사 생성자를 명시적으로 호출 | cs |
1. 객체로 초기화하여 객체가 생성될 때
1 2 3 4 | Person son = father; // Person son(father); // 이 묵시적으로 호출된다 | cs |
1 2 3 | // 위와 헷갈리지 말자 Person = son; son = father; // 복사 생성자 호출되지 않음 | cs |
=> '복사 생성' 과 '복사'의 차이 ★★★★ (성돈이한테 질문함)
2. 'call by value' 로 객체가 전달될 때
=> 앞에서 call by value로 매개 변수 객체가 생성될 때, 생성자가 실행되지 않는다고 하였는데,
생성자 대신에 복사 생성자가 실행된다.
3. 함수가 객체를 리턴할 때
1 2 3 4 5 6 | Person g() { Person mather(2, "Jane"); return mother; /* mother의 복사본을 생성하여 복사본 리턴 사본이 만들어 질 떄 복사 생성자 호출*/ } g(); | cs |
=> g()가 mother 객체를 리턴할 때, mother 객체의 복사본을 만들어 넘겨준다. 복사본을 만들 때 복사 생성자가 호출된다.
'C++ > C++ 문법' 카테고리의 다른 글
#8-1 상속 (0) | 2018.10.07 |
---|---|
#7-B 연산자 중복 (0) | 2018.10.07 |
#07-A 프렌드 (0) | 2018.10.05 |
#6 함수 중복과 static 멤버 (0) | 2018.10.05 |
#5-A 함수와 참조 (0) | 2018.10.04 |