복사 생성자는 말 그대로 원본 객체가 복사될 때 호출됩니다.
case 1. 객체가 매개변수의 인자로 넘어갈 때
case 2. 객체간의 대입연산이 이뤄질 때
case 3. 객체를 반환할 때
3가지 모두 객체의 복사가 일어납니다. 그럴때 복사생성자가 호출이 됩니다.
1,2,3번에 관한 예제코드입니다.
//.h .cpp
class CPerson
{
public:
CPerson(); //기본 생성자
CPerson(const std::string& name, const int& age); //생성자 오버로딩
CPerson(const CPerson& person); //복사 생성자
void Show();
~CPerson(); //소멸자
private:
std::string name;
int* age = nullptr;
};
CPerson GetPerson()
{
CPerson temp;
return temp; //객체 반환
}
CPerson::CPerson()
{
name = "";
age = new int(0);
std::cout << "기본 생성자 호출" << std::endl;
}
CPerson::CPerson(const string& name, const int& age)
{
this->name = name;
this->age = new int(age);
std::cout << "오버로딩 생성자 호출" << std::endl;
}
CPerson::CPerson(const CPerson& person)
{
this->name = person.name;
this->age = new int(*person.age);
std::cout << "복사 생성자 호출" << std::endl;
}
void CPerson::Show()
{
std::cout << "이름은" << name << std::endl;
std::cout << "나이는" << *age << std::endl;
}
CPerson::~CPerson()
{
if (age != nullptr)
delete age;
std::cout << "소멸자 실행" << std::endl;
}
//main
int main()
{
CPerson cp("kim", 30); //오버로딩된 생성자 호출
//cp.Show();
CPerson cpp = cp; //case2 복사 생성자 호출, cpp(cp)형태로 암묵적인 형변환
//이를 막으려면 복사생성자 선언 맨 앞에 explicit 키워드를 붙인다.
//cpp.Show();
CPerson cppp(cp); //case1 복사 생성자 호출
//cppp.Show();
CPerson cpppp = GetPerson(); //case3 복사 생성자 호출
//cpppp.Show();
int a = 10;
return 0;
}
- 총 다섯번의 생성자와, 소멸자가 호출
CPerson cp("kim", 30); -> 오버로딩된 생성자를 호출
CPerson cpp = cp; -> cpp(cp)형태로 암묵적 형변환, 복사생성자 호출
CPerson cppp(cp); -> cppp(cp)로 복사생성자 호출
CPerson cpppp = GetPerson(); -> 함수 내에서 CPerson temp(기본생성자 호출) 후 반환과정에서 복사생성자 호출
- 만약 복사생성자를 정의하지 않는다면 일반 생성자와 똑같이 컴파일러는 default로 복사생성자를 만들어서 호출합니다. 멤버변수 age를 굳이 포인터로 선언한 이유는 이 디폴트 복사 생성자 때문인데요.
복사생성자 정의부분을 한번 보세요. age에 동적할당을 취해줍니다. 디폴트 복사생성자가 호출되면, 사본객체의 age가 원본 객체의 age가 가리키는 주소를 가리키게 됩니다. 즉 같은 주소를 가리키게 되는데요. 만약 원본객체와 사본객체의 소멸자가 차례로 호출된다면 원본객체에서 이미 age를 자원해제 시켰기때문에 사본객체의 age를 delete시키면 에러가 생깁니다. 해당 케이스를 얕은복사(shallow copy)라고 합니다. 완전 제대로 복사가 되지 않은거죠.
예제 코드처럼 복사생성자 내부에서 age에 메모리를 할당시켜줘서 복사를 한다면 깊은복사(deep copy)가 되겠습니다. 모든 객체가 소멸자를 호출해서 age를 delete시켜도 문제가 발생하지 않죠.
- 매개변수를 보면 const CPerson& person 을 보실 수 있습니다. &연산자는 call by reference로 매개변수에 전달되는 객체를 임시복사 하지 않고 그대로 공유(비용절감)하는 방식으로, 이 과정에서 원본의 값이 변하지 않도록 const를 취해주는 것입니다.
call by value, address, reference에 대한 글 보기
상속관계에서 파생클래스의 복사생성자를 선언하는 것 역시 초기화리스트를 이용해서 파생클래스 복사생성자 내에서 기본클래스의 어떤 생성자를 호출할지 선택해주면 되겠죠. 기본클래스의 얕은복사를 막아야 할테니까요.
포스팅 내용에 오류가 있거나 지적사항이 있다면 댓글로 달아주세요. 배움을 멈추지 않는 Good Programmer가 되겠습니다. 감사합니다.
'C++' 카테고리의 다른 글
[C++] 함수재정의 오버로딩(overloading) (0) | 2020.10.09 |
---|---|
[C++] call by value, call by address, call by reference 차이 (0) | 2020.07.21 |
[C++] 생성자(Constructor) 소멸자(Destructor) (0) | 2020.07.19 |
[C++] 업 캐스팅(up casting) & 다운 캐스팅(down casting) (6) | 2020.07.06 |
[C++] 객체지향과 클래스에 대해 - 다형성(Polymorphism) (0) | 2020.06.26 |
댓글