C++ 복사 생성자

C++/C++ 문법 2019. 11. 27. 08:58
728x90

C++ 클래스에는 멤버 변수와 멤버 함수가 있다.

클래스 객체를 생성하면 복사 생성자를 별도로 선언하지 않아도 default 로 복사 생성자(Copy constructor)가 자동으로 생성된다.

만약 생성자 내에서 동적 할당을 한다면 반드시 구현해 주어야 할 것은 소멸자, 복사 생성자이다.

'얕은 복사'는 주소값만 복사하는 것이며, '깊은 복사'는 따로 메모리 할당까지 해주는 것이다.

default copy constructor 는 얕은 복사가 발생하여 소멸자에서 에러가 발생한다.

따라서 개발자가 직접 깊은 복사가 되도록 copy constructor를 구현해 주어야 한다.



C++ 복사 생성자 동영상 강좌를 따라하는데 에러가 발생하고 안된다.

동영상 강좌가 Visual Studio 2017 이전 버전인가 보다. 해결방법은 아래 설명되어 있다.

Person 클래스의 ① 복사 생성자를 구현한 상태로 실행해보고 ② 다시 주석처리하고 실행해보면, 결과가 다르게 나온다는 걸 확인할 수 있다.

예제1.

#include <iostream>
using namespace std;

class Point {
private:
    int x, y;
public:
    Point(int _x=0, int _y=0) : x(_x), y(_y){}
    Point(const Point& p) { // 복사 생성자
        x = p.x;
        y = p.y;
    }
    // 복사 생성자는 명시적으로 생성해주지 않으면 default 복사생성자가 자동으로 생성된다.
    void ShowPosition();
};

void Point::ShowPosition() {
    cout << "(" << x << ", " << y << ")" << endl;
}

class Person {
    char* name;
    char* phone;
    int age;
public:
    Person(char* _name, char* _phone, int _age);
    Person(const Person &p) {
        name = new char[strlen(p.name) + 1]; // 문자열 할당. + 1 은 NULL 문자 고려
        strcpy_s(name, strlen(p.name) + 1, p.name); // 깊은 복사

        phone = new char[strlen(p.phone) + 1];
        strcpy_s(phone, strlen(p.phone) + 1, p.phone);

        age = p.age;
    }
    ~Person();
    void ShowData();
};

Person::Person(char* _name, char* _phone, int _age) {
    name = new char[strlen(_name) + 1];
    strcpy_s(name, strlen(_name) + 1,_name);

    phone = new char[strlen(_phone) + 1];
    strcpy_s(phone, strlen(_phone) + 1,_phone);

    age = _age;
}

Person::~Person() { // DeAllocate the heap
    delete []name;
    delete []phone;
}

void Person::ShowData() {
    cout << "name : " << name << endl;
    cout << "phone : " << phone << endl;
    cout << "age : " << age << endl;
}

int main() {
    Point p1(10, 20); // 객체가 생성될 때 메모리가 할당된다.
    Point p2(p1); // 복사 생성자 호출 발생

    p1.ShowPosition();
    cout << "p1의 주소 값 : " << &p1 << endl;
    p2.ShowPosition();
    cout << "p2의 주소 값 : " << &p2 << endl;

    Person p3("홍길동", "010-1234-5555", 30);
    Person p4(p3);

    p4.ShowData();
    // 객체가 소멸되는 순서는 객체가 생성된 순서의 반대다.
}

에러메시지 내용은

'Person::Person(const Person &)': 인수 1을(를) 'const char [4]'에서 'char *'(으)로 변환할 수 없습니다.


Visual Studio 2017 이상부터는 표준 문법을 엄격히 사용하도록 되어 있다.

Visual Studio 는 문자열 저장방식을 char * 가 아닌 string 사용을 권장하고 있다.

에러 메시지를 해결하는 방법으로 해당 파일에서 마우스 우클릭 누르면 속성이 나온다.

C/C++ → 언어 → 준수모드 : 아니오 로 변경하면 에러 메시지 없이 결과가 출력된다.

이것이 올바른 해결책은 아니다.

에러 메시지에 해당되는 https://docs.microsoft.com/ko-kr/cpp/error-messages/compiler-errors-2/compiler-error-c2664?f1url=https%3A%2F%2Fmsdn.microsoft.com%2Fquery%2Fdev16.query%3FappId%3DDev16IDEF1%26l%3DKO-KR%26k%3Dk(C2664)%26rd%3Dtrue&view=vs-2019 를 차분히 읽어보니 해결책은 따로 있다.


const char* _name, const char* _phone 로 const 를 붙이면 해결된다.


copy_constructor.cpp



다른 방법 : char* _name 을 const char _name[] 으로 변경해주고, 선언과 정의를 분리했다.

예제2에서 결과 출력이 잘 되는 걸 확인할 수 있다.

예제2.

#include <iostream>
#include <cstring>
using namespace std;

class Person {
    char* name;
    char* phone;
    int age;
public:
    Person(const char _name[], const char _phone[], int _age);
    ~Person();
    Person(const Person& p);
    void ShowData(); // 선언
};

Person::Person(const char _name[], const char _phone[], int _age) {
    cout << "생성자 호출" << endl;
    name = new char[strlen(_name) + 1];
    strcpy_s(name, strlen(_name) + 1, _name);

    phone = new char[strlen(_phone) + 1];
    strcpy_s(phone, strlen(_phone) + 1, _phone);

    age = _age;


    cout << "name 주소 : " << (void*)name << endl;
    cout << "phone 주소 : " << (void*)phone << endl;
}

Person::~Person() {
    cout << "소멸자 호출" << endl;
    delete[]name; // 동적할당한 메모리 해제
    delete[]phone;
// 동적할당한 메모리 해제


    cout << "name 주소 해제 : " << (void*)name << endl;
    cout << "phone 주소 해제 : " << (void*)phone << endl;
}

Person::Person(const Person& p) : age(p.age) {
    cout << "복사 생성자 호출" << endl;
    name = new char[strlen(p.name) + 1]; // 문자열 할당. +1은 NULL문자(\0) 고려
    strcpy_s(name, strlen(p.name) + 1, p.name); // 깊은 복사

    phone = new char[strlen(p.phone) + 1];
    strcpy_s(phone, strlen(p.phone) + 1, p.phone);


    cout << "name 주소 : " << (void*)name << endl;
    cout << "phone 주소 : " << (void*)phone << endl;
}

void Person::ShowData() { // 클래스 외부에서 클래스 멤버 함수 정의
    cout << "name : " << name << endl;
    cout << "phone : " << phone << endl;
    cout << "age : " << age << endl;
}

int main() {
    Person p1("홍길동", "010-1234-5555", 30);
    p1.ShowData();

    Person p2(p1); // 복사 생성자 호출
    p2.ShowData();
    // 객체가 소멸되는 순서는 객체가 생성된 순서의 반대다.
}

생성자 호출이 되고, 복사 생성자가 호출된다.

소멸자가 두번 호출되는 걸 확인할 수 있고 소멸된 순서를 확인할 수 있다.



이번에는 char *name 대신에 string 으로 변경하여 #include <string> 를 추가하고 코드를 수정해서 테스트했다.

결과는 잘 출력된다.


예제3.

#include <iostream>
#include <string>
using namespace std;

class Point {
private:
    int x, y;
public:
    Point(int _x=0, int _y=0) : x(_x), y(_y){}
    Point(const Point& p) { // 복사 생성자
        x = p.x;
        y = p.y;
    }
    // 복사 생성자는 명시적으로 생성해주지 않으면 default 복사생성자가 자동으로 생성된다.
    void ShowPosition();
};

void Point::ShowPosition() {
    cout << "(" << x << ", " << y << ")" << endl;
}

class Person {
    string name;
    string phone;
    int age;
public:
    Person(string _name, string _phone, int _age);
    ~Person();
    Person(const Person& p); // Copy Constructor

    //Setter method
    void setName(string name) {    this->name = name; }
    void setPhone(string phone) { this->phone = phone; }
    void setAge(int age) { this->age = age; }

    // Getter method
    string getName() { return name; }
    string getPhone() { return phone; }
    int getAge() { return age; }

    void ShowData();
};

Person::Person(string _name, string _phone, int _age) {
    cout << "Person 생성자 호출- 메모리 주소 : " << this << endl;
    name = _name;
    phone = _phone;
    age = _age;
}

Person::~Person() {
    cout << "Person 소멸자 호출- 메모리 주소 : " << this << endl;
}

Person::Person(const Person& p) {
    cout << "Person 복사 생성자 호출 - 메모리 주소 : " << this << endl;
    name = p.name;
    phone = p.phone;
    age = p.age;
}

void Person::ShowData() {
    cout << "name : " << name << endl;
    cout << "phone : " << phone << endl;
    cout << "age : " << age << endl;
}

int main() {
    Point p1(10, 20); // 객체가 생성될 때 메모리가 할당된다.
    Point p2(p1);

    p1.ShowPosition();
    cout << "p1의 주소 값 : " << &p1 << endl;
    p2.ShowPosition();
    cout << "p2의 주소 값 : " << &p2 << endl << endl;

    Person p3("홍길동", "010-1234-5555", 30);
    Person p4(p3);

    cout << endl;
    p4.ShowData();
    // 객체가 소멸되는 순서는 객체가 생성된 순서의 반대다.
}


Person 클래스의 생성자 호출, 복사 생성자 호출, 소멸자 호출이 2번 되는 걸 확인할 수 있다.

'C++ > C++ 문법' 카테고리의 다른 글

C++ 복사 대입 연산자(copy assignment operator)  (0) 2019.11.28
C++ 얕은 복사, 깊은 복사(Shallow Copy vs. Deep Copy)  (0) 2019.11.27
C++ 연산자 오버로딩  (0) 2019.11.26
C++ static  (0) 2019.11.25
C++ 클래스 이해  (0) 2019.11.23
블로그 이미지

Link2Me

,