복사 생성자
같은 클래스의 객체로부터 '복사'해서 새로운 객체를 생성하는 생성자이다.
class Sample {
int num1;
int num2;
public:
Sample(int a, int b);
Sample(const Sample& Sp);
};
// 일반 생성자
Sample::Sample(int a, int b) {
std::cout << "일반 생성자" << std::endl;
num1 = a;
num2 = b;
}
// 복사 생성자
Sample::Sample(const Sample& Sp) {
std::cout << "복사 생성자" << std::endl;
num1 = Sp.num1;
num2 = Sp.num2;
}
이와 같이 클래스를 선언했다. Sample(const Sample& Sp)
는 복사 생성자의 표준 정의이다. main()
함수를 보면
int main()
{
Sample sp1(3,3);
Sample sp2(sp1);
Sample sp3 = sp2;
}
와 같이 세가지 방법으로 객체를 생성할 수 있다. 전체 코드를 실행하면
일반 생성자
복사 생성자
복사 생성자
와 같이 출력됩니다. 여기서 복사 생성자는 생성자의 인수로 다른 객체를 상수(const
) 레퍼런스로 받기 때문에 생성자 내부에서 해당 객체의 정보를 변경할 수 없고 복사만 가능합니다. 이때 const
키워드를 붙이는 이유는 함수 내에서 변경되지 않는 객체라는 것을 명시적으로 나타내기 위함입니다.
디폴트 복사 생성자
그런데 첫번째와 두번째 라인은 위에서 클래스를 정의할 때 함께 정의한 생성자와 생성자 오버로딩인데 세번째 라인은 왜 오류 없이 실행되었을까요? 이는 C++컴파일러의 기본 복사 생성자 기능 때문입니다.
한 가지 주의사항은 디폴트 복사 생성자는 '얕은 복사'를 수행합니다. 이 말은 어떤 멤버 변수의 값과 주소가 모두 같다는 것입니다. 만일 일반 생성자에서 동적 할당을 수행하고, 해당 메모리를 포인터로 가리키고 있는 경우, 일반적으로 소멸자에서 해당 메모리를 해제하게 됩니다. 그런데 디폴트 복사 생성자는 얕은 복사만을 수행하므로 복사받은 객체가 삭제되고 동적 할당된 메모리도 해제되면, 복사된 객체가 해당 주소에 접근하면 런타임 오류가 발생합니다. 따라서 이런 경우에는 복사 생성자를 개발자가 직접 지정해 새롭게 동적 할당을 수행해주어야 합니다.
생성자의 인자로 레퍼런스를 받는 이유
복사 생성 시 인자로 레퍼런스가 아닌 객체를 받게 되면, 함수 내부에 진입하면서 해당 객체를 복사해서 가져오게 됩니다. 복사 생성자는 복사 대상의 정보를 변경하지 않는, 인자가 const
타입인 함수입니다. 따라서 새로운 객체를 생성해 오버헤드가 생기는 것을 방지하기 위해 레퍼런스로 인자를 받는 것입니다.
마찬가지 이유로 다음 세 경우에도 새로운 객체가 필요하므로 복사 생성자가 호출됩니다.
- (디폴트) 복사 생성자로 대입 연산을 수행
- 매개변수로 들어갈 때
- 반환값으로 주어질 때
반드시 새로운 객체를 생성해야 하는 경우가 아니고, 값을 참조하거나 단순히 값을 변경하는 경우에는 레퍼런스를 사용하면 오버헤드가 사라집니다.