복사 생성자, 복사 대입연산자

Copy Constructor, Copy Assign Operator

 

복사 생성자와 복사 대입 연산자에 대한 개념을 이해하는 것이 이 글의 목적이다. 둘의 개념을 이해하기 위해 이와 관련된 몇가지들을 추가로 함께 설명할 것이다. 바로 아래 작성된 코드를 이해할 수 있다면 C++의 복사 생성자와 복사 대입 연산자를 어렵지 않게 사용할 수 있을 것이라고 생각된다.

 

 

샘플 전체 코드

#include<iostream>
using namespace std;

class Joker {
    public:
        Joker();
        Joker(const Joker& rhs);
        Joker& operator=(const Joker& rhs);
        bool validate1(Joker rhs);
        bool validate2(const Joker& rhs);
};

Joker::Joker() {
    cout << "call constructor" << endl;
}

Joker::Joker(const Joker& rhs) {
    cout << "call copy constructor" << endl;
}

Joker& Joker::operator=(const Joker& rhs) {
    cout << "call copy operator=" << endl;
    return *this;
}

bool Joker::validate1(Joker joker) {
    cout << "call validate1" << endl;
    return true;
}

bool Joker::validate2(const Joker& joker) {
    cout << "call validate2" << endl;
    return true;
}

int main()
{
    // constructor
    Joker jk;

    // copy constructor
    Joker jk2(jk);
    Joker jk3 = jk;
    
    // copy operator
    jk2 = jk;

    // call method with copy constructor
    jk.validate1(jk2);
    jk.validate2(jk2);
    return 0;
}

 

실행 결과 :

call constructor

call copy constructor
call copy constructor

call copy operator=

call copy constructor
call validate1
call validate2

 

 

 

생성자란

생성자(constructor)는 객체를 생성할 때 호출되는 함수를 말한다.

// 함수 정의
Joker();

// 함수 호출
Joker jk;

 

실행 결과 :

call constructor

 

 

 

복사 대입 연산자란

복사 대입 연산자(copy assign operator)는 객체를 대입할 때 호출되는 연산자를 말한다. C++에서는 복사 생성자와 복사 대입 연산자가 조금 헷갈리는 경우가 있다. 예를 들어, “Joker jk2 = jk’;” 문장은 복사 생성자가 호출되지만, “Joker jk2; jk2 = jk;”는 기본 생성자가 호출된 후, 복사 대입 연산자가 호출된다. 두 코드는 모두 “=”라는 연산기호를 사용하지만 실제 호출되는 함수는 다르다. 이 글을 보고 두 경우가 다르게 동작한다는 것을 이해하고 넘어가길 바란다.

// 함수 정의
Joker& operator=(const Joker& rhs);

// 함수 호출
Joker jk;
Joker jk2;
jk2 = jk;

 

실행 결과 :

call constructor
call constructor
call copy operator=

 

 

 

복사 생성자란

복사 생성자(copy constructor)는 앞에서 설명한 생성자(constructor)와 마찬가지로 객체가 생성될 때 호출되는 함수이다. 하지만, 복사를 할 때만 호출된다는 특징을 가진다. 복사 생성자가 호출되는 3가지 경우(case)를 보고 이해해보려 한다.

 

  1. case1 : 복사 생성자 호출
  2. case2 : (대입 연산자로) 복사 생성자 호출
  3. case3 : 함수 호출 (=값에 의한 호출)

 

 

case1. 복사 생성자 호출

생성자의 종류 중 하나인 복사 생성자를 직접 호출하는 경우이다.

// 함수 정의
Joker(const Joker& rhs);

// 함수 호출
Joker jk;
Joker jk2(jk); // case1

 

실행 결과 :

call constructor
call copy constructor

 

 

case2. (대입 연산자로) 복사 생성자 호출

case2 case1처럼 동일하게 복사 생성자를 호출하지만, 아래 예시처럼 호출하는 코드만 다를 뿐이다.

// 함수 정의
Joker(const Joker& rhs);

// 함수 호출
Joker jk;
Joker jk2 = jk; // case2

 

실행 결과 :

call constructor
call copy constructor

 

 

case3. 함수 호출 (=값에 의한 호출)

case3는 함수의 호출 방식 중에 값에 의한 호출(call by value or pass by value)을 말하는 것이다. 아래 코드를 보면 validate() 함수안에 입력 파라미터로 객체를 받도록 정의된 것을 볼 수 있다. 이 함수를 호출할 때는 입력 파라미터를 위한 객체를 생성하게 되는데, 이때 복사 생성자가 호출되는 것이다.

// 함수 정의
Joker(const Joker& rhs);
bool validate(Joker rhs);

// 함수 호출
Joker jk;
Joker jk2;
jk.validate(jk2); // case3

 

실행 결과 :

call constructor
call constructor
call copy constructor
call validate

 

case3와 함께 살펴보면 좋은 내용이 있어서 한가지 예시를 추가로 들려고 한다. 설명하려는 내용은 case3에서 먼저 설명한 값에 의한 호출(call by value)참조에 의한 호출(call by reference)이다. case3의 코드를 참조에 의한 호출 방식으로 변경하면 아래와 같고, 이 땐 주소값을 참조할 뿐이 때문에 복사 생성자가 호출되지 않는다.

// 함수 정의
Joker(const Joker& rhs);
bool validate(const Joker& rhs);

// 함수 호출
Joker jk;
Joker jk2;
jk.validate(jk2); // (case3과는 다르게) 복사 생성자가 호출되지 않음

 

실행 결과 :

call constructor
call constructor
call validate