Notice
Recent Posts
Recent Comments
«   2024/12   »
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
Archives
Today
Total
관리 메뉴

SYDev

C++ Chapter 05-1 : 복사 생성자(Copy Constructor) 본문

Programming Lang/C++

C++ Chapter 05-1 : 복사 생성자(Copy Constructor)

시데브 2023. 7. 16. 20:01

복사 생성자(Copy Constructor)

 우리는 지금까지 다음 방식으로 변수와 참조자를 선언 및 초기화했다.

int num = 20;
int &ref = num;

 하지만 C++에서는 다음과 방식으로도 선언 및 초기화가 가능하다.

int num(20);
int &ref(num);

 C++은 위와 같이 두 가지 초기화 방식을 동시에 지원하고 있다.

 

 이어서 객체의 생성으로 넘어가보자. 아래에 간단한 클래스 하나가 있다.

class SoSimple
{
private:
    int num1;
    int num2;

public:
    SoSimple(int n1, int n2) : num1(n1), num2(n2)
    {
    }
    void ShowSimpleData()
    {
        cout << num1 << endl;
        cout << num2 << endl;
    }
};

int main(void)
{
    SoSimple sim1(15, 20);
    SoSimple sim2 = sim1; // sim2객체를 새로 생성해서 sim2의 멤버에 sim1의 멤버가 복사된다.
    sim2.ShowSimpleData();

    return 0;
}

 위에서 주석이 작성된 코드는 객체 멤버의 복사를 의미한다. 또한 변수 초기화와 마찬가지로, 다음 두 문장도 동일하게 해석된다.

SoSimple sim2 = sim1;
SoSimple sim2(sim1);

 sim2 역시 객체이기 때문에 생성자의 호출이 필수적일 것이다. 그렇다면 해당 객체의 생성자 형태는 다음과 같다고 유추할 수 있다.

SoSimple(SoSimple &copy)
{
	. . . . 
}

 그렇다면 위 형태로 생성자를 선언하는 예제를 살펴보자.

#include <iostream>
using namespace std;

class SoSimple
{
private:
    int num1;
    int num2;

public:
    SoSimple(int n1, int n2)
        : num1(n1), num2(n2)
    {
        // empty
    }

    SoSimple(SoSimple &copy) // 복사 생성자
        : num1(copy.num1), num2(copy.num2)
    {
        cout<<"Called SoSimple (SoSimple &copy)"<<endl;
    }

    void ShowSimpleData()
    {
        cout<<num1<<endl;
        cout<<num2<<endl;
    }
};

int main(void)
{
    SoSimple sim1(15, 30);
    cout<<"생성 및 초기화 직전"<<endl;
    SoSimple sim2 = sim1; // SoSimple sim2(sim1); 으로 자동 변환
    cout<<"생성 및 초기화 직후"<<endl;
    sim2.ShowSimpleData();

    return 0;
}
생성 및 초기화 직전
Called SoSimple &copy
생성 및 초기화 직후
15
30

 위 예제에서 17행에 정의된 생성자를 가리켜 '복사 생성자(copy constructor)'라고 부른다. 또한, '복사 생성자'라는 이름이 붙은 것은 그저 다른 생성자와 달리 정의가 특이해서가 아니라 호출되는 시점이 다른 생성자들과 차이가 있기 때문이다. 

 

 그리고 일반적인 복사생성자의 형태는 위에서 조금 바뀐 다음 모습이다.

    SoSimple(const SoSimple &copy)
        : num1(copy.num1), num2(copy.num2)
    {
        cout<<"Called SoSimple(SoSimple &copy)"<<endl;
    }

 복사라는 것은 원본 내용이 바뀌면 개념이 무너지기 때문에 이러한 실수를 막기 위해 키워드 const를 삽입해주는 것이 좋다.

 

디폴트 복사 생성자

 그런데 위에서 처음 정의한 SoSimple 클래스는 복사 생성자를 정의하지 않았다. 그런데도 객체 멤버 대 멤버 복사는 문제 없이 진행되었다. 그렇다면 우리는 복사 생성자도 기본적인 생성자와 마찬가지로 디폴트 생성자를 가지고 있다고 유추할 수 있다. 실제로, 복사 생성자를 따로 정의하지 않으면(생성자가 존재하더라도) 멤버 대 멤버 복사를 진행하는 "디폴트 복사 생성자"가 자동으로 삽입된다.

 

 따라서 아래 클래스의 정의는,

class SoSimple
{
private:
    int num1;
    int num2;

public:
    SoSimple(int n1, int n2) : num1(n1), num2(n2)
    { }
    . . . . .
};

 다음의 클래스 정의와 완전히 동일하다.

class SoSimple
{
private:
    int num1;
    int num2;

public:
    SoSimple(int n1, int n2) : num1(n1), num2(n2)
    { }
    SoSimple(const SoSimple &copy) : num1(copy.num1), num2(copy.num2)
    { }
};

 

키워드 explicit

 앞서 복사 생성자에 관해서 다음 문장이,

SoSimple sim2 = sim1;

 다음 형태로 묵시적 변환이 일어나 복사 생성자가 호출된다고 설명했다.

SoSimple sim2(sim1);

 이런 묵시적 변환을 임의로 막을 수 있는 키워드가 explicit이다. 따라서 위 복사생성자를 다음과 같이 바꾸면 묵시적 변환은 더이상 발생하지 않는다.

explicit SoSimple(const SoSimple &copy)
    : num1(copy.num1), num2(copy.num2)
{
    // empty
}

 또한 이러한 묵시적 변화는 복사 생성자의 경우에서만 일어나는 게 아니다. 전달인자가 하나인 생성자가 있으면, 이 역시 묵시적 변환이 발생할 수 있다. 

 

 다음 클래스가 있다면,

class AAA
{
private:
    int num;
public:
    AAA(int n) : num(n) { }
    . . . . .
};

다음과 같은 상황에서 묵시적 변환이 일어난다.

AAA obj = 3; /// AAA obj(3); 으로 변환

 이러한 상황에서 키워드 explicit을 사용하면 다음 형태로 객체를 생성할 수밖에 없다.

AAA obj(3);

 


출처 : 윤성우, <윤성우의 열혈 C++ 프로그래밍>, 오렌지미디어, 2010.05.12