18. [C++]복사 생성자(Copy Constructor)

2024. 4. 15. 10:43[C++]/C++ 언어 기초

C++ 스타일의 초기화

기존 변수, 참조자 선언 및 초기화 방법

int num = 20;
int &ref = num;

 

C++에서의 변수, 참조자 선언 및 초기화

int num(20);
int &ref(num);
  • 위 두 가지 초기화 방식은 결과적으로 동일하다.
  • C++에서는 위 두 가지 방식의 초기화를 지원한다.

 

#include "stdafx.h"
#include <iostream>

using namespace std;
using namespace System;

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에 sim1을 복사
	sim2.ShowSimpleData();

	return 0;
}
15
20
  • sim2 객체를 새로 생성해서, 객체 sim1과 sim2간의 멤버 대 멤버 복사가 일어난다고 예상할 수 있다.

int num1 = num2;
int num1(num2);
  • 이 문장이 동일하듯이
SoSimple sim2 = sim1;
Sosimple sim2(sim1);
  • 이 두 문장도 동일한 의미로 해석이 된다.

SoSimple sim2(sim1)

문장의 의미는 다음과 같다.

  • SoSimple형 객체를 생성해라
  • 객체의 이름은 sim2로 정한다.
  • sim1을 인자로 받을 수 있는 생성자의 호출을 통해서 객체생성을 완료한다.

 

SoSimple(SoSimple &copy){
...
}
  • 위의 객체 생성문에서 호출하고자 하는 생성자는 SoSimple 객체를 인자로 받을 수 있는 생성자이다.

 

1. SoSimple sim2 = sim2;
2. SoSimple sim2(sim1);
  • 1번 문장도 실은 2번의 형태로 묵시적 변환이 되어서 객체가 생성되는 것이다.

 

ClassInit.cpp

#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(SoSimple &copy)
생성 및 초기화 직후
15
30
  • 위 예제에서 SoSimple sim2 = sim1; 부분은 묵시적으로 변환된다.

 

 

SoSimple(SoSimple &copy)
		: num1(copy.num1), num2(copy.num2)
	{
		cout<<"Called SoSimple(SoSimple &copy)"<<endl;
	}
  • 예제에서 이러한 생성자를 가리켜 별도로 "복사 생성자"라고 한다.
  • 이 생성자는 호출되는 시점이 다른 일반 생성자와 차이가 있다.
SoSimple(const SoSimple &copy)
		: num1(copy.num1), num2(copy.num2)
	{
		cout<<"Called SoSimple(SoSimple &copy)"<<endl;
	}
  • 또한 멤버 대 멤버의 복사에 사용되는 원본을 변경시키는 것은 복사의 개념을 무너뜨리는 행위가 되니, 키워드 const를 삽입해 이러한 실수를 막아 놓는 것이 좋다.

자동으로 삽입이 되는 디폴트 복사 생성자

  • 복사 생성자를 정의하지 않으면, 멤버 대 멤버의 복사를 진행하는 디폴트 복사 생성자가 자동으로 삽입된다.

  • 생성자가 존재하더라도, 복사 생성자가 정의되어 있지 않으면 디폴트 복사 생성자라는 것이 삽입되어 멤버 대 멤버의 복사를 진행한다.
  • 따라서 위의 코드는 완전히 동일하다. 디폴트 복사 생성자가 자동으로 삽입되기 때문이다.

변환에 의한 초기화! 키워드 explicit으로 막을 수 있다

SoSimple sim2 = sim1;
  • 위 코드는 묵시적 변환이 일어나서 복사 생성자가 호출된다고 설명했다.
SoSimple sim2(sim1);
  • 이는 결국, 복사 생성자가 묵시적으로 호출된 것이다.
  • explicit 키워드는, 복사 생성자의 묵시적 호출을 허용하지 않게 해주는 키워드이다. 

 

explicit SoSimple(const SoSimple &copy) : num1(copy.num1), num2(copy.num2){
	//empty!
}
  • 이렇게 explicit 키워드를 사용하게 되면 묵시적 변환이 발생하지 않아 대입 연산자를 이용한 객체의 생성 및 초기화가 불가능하다.

 

이러한 문장의 묵시적 변환은 복사 생성자에서만 일어나는 게 아니다.

전달인자가 하나인 생성자가 있다면, 이 역시 묵시적 변환이 발생한다.

class AAA{
private:
	int num;
public:
	AAA(int n) : num(n){}
    -----
};
  • 이 코드는 AAA obj = 3;   //AAA obj(3)으로 변환된다.
  • 이 경우에도 마찬가지로 explicit 키워드를 사용해주면 AAA obj(3)으로만 생성이 가능해진다.
반응형