DevNote

복사 생성자(copy constructor) 본문

C++

복사 생성자(copy constructor)

Ahnda 2020. 2. 15. 22:33

 C++에서 복사 생성자란 자신과 같은 클래스 타입의 다른 객체에 대한 참조(reference)를 인수로 전달받아, 그 참조를 가지고 자신을 초기화하는 방법이다. 복사 생성자는 새롭게 생성되는 객체가 원본 객체와 같으면서도, 완전한 독립성을 갖게 한다. 왜냐하면, 복사 생성자를 이용한 대입은 깊은 복사를 통한 값의 복사이기 때문이다.

 

 그러면! 깊은 복사란 무엇인가? 복사 생성자를 설명할 때, 꼭 빠지지 않는 개념이 바로 얕은 복사와 깊은 복사이다.

 

<얕은 복사와 깊은 복사>

새롭게 생성하는 변수에 다른 변수의 값을 대입하기 위해서는 대입 연산자(=)를 사용한다. 이와 마찬가지로 클래스 객체도 새롭게 생성될 때, 다른 객체의 값을 대입하기 위해 대입 연산자를 사용할 수 있다. 아래의 예제를 보면 좀 더 이해하기 쉬울 것이다. 

#include <iostream>
using namespace std;

class A
{
private:
    int num;
    int size;

public:
    A() {num = 0; size = 0;}
    A(int in_num, int in_size)
    {
    	num = in_num;
        size = in_size;
    }
    
    ~A()
    {
    	cout << "소멸자가 호출되었습니다." << endl;
    }
    
    void Show()
    {
    	cout << num << "번째 크기는 " << size << "입니다." << endl;
    }
};

int main()
{
    int a = 28;
    int b = a;
    
    A class_a(1, 28);
    A class_b = a;
    
    return 0;
}

 

 그러나, 대입 연산자를 이용한 객체의 대입은 얕은 복사로 수행된다. 얕은 복사란 값을 직접적으로 복사하는 것이 아닌, 값을 가리키는 주소(포인터)를 복사하는 것이다. 이렇게 되면, 변수의 생성에서 대입 연산자를 이용한 값의 복사는 큰 문제가 안되지만, 객체에서는 문제가 발생할 수도 있다. 특히 객체의 멤버가 메모리 공간의 힙(heap) 영역을 참조할 경우에 문제가 발생할 수도 있다. 아래의 예제를 통해 얕은 복사의 문제점을 쉽게 확인할 수 있다.

 

    A a(1, 28, "Hola");
    A b = a;             // 얕은 복사
    
    // 객체 a의 값만 바꿔줌
    a.num = 2;
    a.size = 30;
    strcpy(a.name, "Ni Hao");

    a.Show();

    cout << endl;

    b.Show(); 
    
    return 0;

우리는 객체 a의 값만 바뀌었을 것으로 예상할 수 있지만, 포인터로 선언된 name 멤버에 대해서는 a와 b 모두 값이 바뀌어 있는 것을 결과를 통해 확인할 수 있다. 이를 통해 얕은 복사를 하게 되면 a객체와 b객체의 name 멤버가 하나의 메모리 주소를 공유하는 것을 의미한다.

 

즉, 얕은 복사를 통해 객체를 복사하게 되면, 힙에 할당된 멤버는 서로 다른 객체 간의 힙 포인터를 공유하므로 하나만 수정하길 의도한 코드에서 두 객체의 값이 모두 변하는 예상치 못한 상황에 놓일 수도 있다.

 

따라서 객체에 대한 복사는 값을 가리키는 주소뿐만 아니라, 값을 직접적으로 복사하는 깊은복사를 이용하는 것이 안전하다. 복사 생성자를 이용한 대입은 앞서 말했듯이 깊은 복사를 이용한 값의 복사다. 아래의 예제는 A 클래스의 복사 생성자이다.

A(const A&)

<예제>

A::A(const A& in_A)	//복사생성자의 선언
{
	num = in_A.num;
    size = in_A.size;
}

int main()
{
	A a(1, 28);
    A b(a);
    
    a.Show();
    b.Show();
	
	return 0;
}

<결과>

더보기

1번째 크기는 28입니다.

2번째 크기는 28입니다.

 

위의 예제는 복사생성자를 이용해 새롭게 생성되는 b 객체는 같은 클래스의 a 객체로 초기화한다. A b(a); 구문은 컴파일러에 의해 다음과 같이 복사 생성자를 사용한 것으로 해석된다.

 

A b = A(a);

'C++' 카테고리의 다른 글

참조자(reference)  (0) 2020.02.17