|5.5| 복사 생성자

● 얕은 복사와 깊은 복사 (shallow copy & deep copy)

Person 객체의 멤버 변수로 

int id;
char *name;

이 있다고 가정하자.

얕은 복사의 경우, name 포인터로 문자열 배열을 공유하기 때문에 사본 객체에서 name 문자열을 변경하면,
원본 객체의 name 문자열이 변경되는 문제가 발생한다.

깊은 복사는 원본의 name 포인터가 가리키는 메모리까지 복사하여 원본과 사본의 name은 별개의 메모리를 가리킨다.

=> 가능하면 얕은 복사가 일어나지 않도록 해야 한다.


● 복사 생성 및 복사 생성자



복사 생성자 선언


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