'복사 생성자'는 다른 인스턴스의 값을 그대로 복사해서 새로운 인스턴스를 생성할 때 사용됩니다.

'복사 생성자'가 호출되는 경우는 2가지가 있습니다.

   1. 명시적으로 객체의 복사본을 생성하는 방식으로 선언

   2. 함수 형태로 호출되는 경우

      - 클래스가 매개변수로 사용되는 경우

      - 클래스가 반환 형식으로 사용되는 경우

 

 

1. 명시적으로 객체의 복사본을 생성하는 방식으로 선언

먼저 첫 번째 경우를 코드로 살펴보겠습니다.

void main() {
    int a = 10;
    int b = a;
}

 

위의 예제에서 'a'의 값을 'b'에 대입(복사)하면서 생성하였습니다. 이 원리를 클래스에 적용시킨 것이 '복사 생성자'입니다. 

class MyData {
private:
    int m_nData = 0;
    
public:
    MyData() { /* do somthing */ }
    
    MyData(const MyData& rhs) : m_nData{ rhs.m_nData } { /* do somthing */ }
    
    ~MyData() { /* do somthing */ }
    
    int GetData() const { return m_nData; }
    
    void SerData(int nParam) { m_nData = nParam; }
}

void main() {
    MyData a;  // 디폴트 생성자가 호출됩니다.
    a.SetData(10);
    
    MyData b{ a };
    cout << b.GetData() << endl;
}   

 

 

2. 함수 형태로 호출되는 경우

함수 형태로 '복사 생성자'가 호출되는 경우는 클래스가 반환 타입인지 매개변수인지에 따라 나누어집니다. 이 중, 클래스가 반환 형식인 함수는 '이름 없는 임시 객체'를 만들어내니 주의해야합니다.

예제를 살펴보겠습니다.

class CTestData {
private:
    int m_nData = 0;
    
public:
    CTestData(int nParam) : m_nData{ nParam } { cout << "CTestData(int)" << endl; }
    
    CTestData(const CTestData& rhs) : m_nData{ rhs.m_nData } { cout << "CTestData(const CTestData&)" << endl; }
    
    int GetData() const { return m_nData; }
    
    void SetData(int nParam) { m_nData = nParam; }
}

// 매개변수가 CTestData 타입입니다. 당연히 해당 스코프에서 사용되기 위해선 변수가 '생성'되어야합니다.
// 호출자가 넘긴 인스턴스의 값을 복사해서 새로운 인스턴스를 생성합니다. 
// 이때, '복사 생성자'가 호출됩니다. 
void TestFunc(CTestData param) {
    cout << "TestFunc()" << endl;
    param.SetData(20); 
}

void main() {
    CTestData a{ 10 };
    
    TestFunc(a); // 함수 내에선 'a' 인스턴스의 값을 '복사'해서 새로운 인스턴스를 만듭니다.
    
    cout << "a: " << a.GetData() << endl;
}

여기서 문제는 TestFunc() 함수에서 매개변수로 넘어온 CTestData 인스턴스를 '복사'해서 새로 '생성'한다는겁니다. 쓸 데 없이 인스턴스 하나를 '생성'하는데 드는 비용과 '복사'하는데 드는 비용이 발생합니다. 사실 함수를 이렇게 사용할거라면 굳이 C++를 사용할 이유가 없습니다. 간단히 &(참조자)로 받으면 이 문제는 싹 해결됩니다.

(참고로, *(포인터)댕글링 포인터, 의존 관계 분석 불가로 인한 코드 최적화 실패 등의 단점이 있습니다. 이러한 *(포인터)의 단점을 보완해서 나온게 &(참조자)입니다.)