C++은 변수/참조자에 대하여 기본적인 초기화 방법 외에도 객체 생성시 생성자를 이용해 초기화 했던 방식으로 선언 및 초기화를 할 수 있도록 지원한다.
int num = 20;
int num(20)
복사생성자가 어떤일을 하는지 이해하기 위해서 일단 주어진 예시코드와 그 실행결과를 보고 복사생성자의 기능을 예측해보자.
#include <iostream>
using namespace std;
class SimpleClass {
private:
int num1;
int num2;
public:
SimpleClass(int num1, int num2) {
this->num1 = num1;
this->num2 = num2;
cout<<"생성자 호출"<<endl;
}
void Show() {
cout<<"num1: "<<num1<<endl;
cout<<"num2: "<<num2<<endl;
}
};
int main(){
SimpleClass cls1(100, 200);
SimpleClass cls2 = cls1; /*복사 생성자*/
cout<<"=== 클래스 1의 멤버변수 값 ==="<<endl;
cls1.Show();
cout<<"=== 클래스 2의 멤버변수 값 ==="<<endl;
cls2.Show();
return 0;
}
SimpleClass cls2;
cls2 = cls1;
클래스의 객체를 인자로 받아, 멤버를 복사하는 생성자를 복사 생성자(copy constructor)라고 부른다.
const
키워드를 사용한다.&
SimpleClass(const SimpleClass ©cls)
: num1(copycls.num1), num2(copycls.num2) { }
SimpleClass cls2 = cls1;
(대입 연산 이용)
SimpleClass cls2(cls1);
(생성자 호출)
→ 따라서 대입 연산자를 이용한 방식으로 객체를 생성하면, 두 번째 방식으로 묵시적 변환이 일어나서 복사 생성자가 호출된다.
explicit
키워드를 붙여 묵시적 변환이 발생하지 않게함으로서 대입연산자를 이용한 객체 생성 및 초기화를 막을 수 있다.SimpleClass cls = 10;
SimpleClass cls(10);
얕은 복사 방식은 멤버변수가 힙(HEAP)의 메모리 공간을 참조하는 경우에 문제가 된다.
HEAP에 할당된 멤버(문자열, 객체…)는 참조를 이용하여 접근하기 때문에, 변수 자체에는 참조하려는 주소값이 저장되어있다.
따라서 참조변수를 얕은 복사 방식으로 복사하게 되면 주소 값이 복사되기 떄문에 동일한 주소를 참조하는 새로운 멤버를 만들게 된다.
이로 인해 발생하는 문제점
얕은 복사의 문제점이 발생하는 예제 코드
name
)까지 함께 변경되었다.name
이 원본 객체가 소멸되면서 사라져, 복사본 객체가 소멸자를 호출하면 제대로 동작하지 않아 “소멸자 호출" 문장이 1번만 출력되었다. #include <iostream>
using namespace std;
class Student {
private:
int num;
char* name;
public:
Student(int num, char* name) {
this->num = num;
int len = strlen(name) + 1;
this->name = new char[len];
strcpy(this->name, name);
cout<<"생성자 호출"<<endl;
}
void SetStudentInfo(int num, char* name) {
this->num = num;
int len = strlen(name) + 1;
strcpy(this->name, name);
}
void GetStudentInfo() {
cout<<"num: "<<num<<endl;
cout<<"name: "<<name<<endl;
}
//소멸자
~Student() {
delete []name;
cout<<"소멸자 호출"<<endl;
}
};
int main(){
Student ori(1800, "홍길동");
Student copy = ori; /*복사 생성자*/
cout<<"=== 원본 객체의 멤버변수 값 ==="<<endl;
ori.GetStudentInfo();
cout<<"=== 복사본 객체의 멤버변수 변경 ==="<<endl;
copy.SetStudentInfo(2000, "전우치");
cout<<"=== 원본 객체의 멤버변수 값 ==="<<endl;
ori.GetStudentInfo();
cout<<"=== 복사본 객체의 멤버변수 값 ==="<<endl;
copy.GetStudentInfo();
return 0;
}
깊은 복사 방법을 이용하고자 한다면, 단순히 참조변수에 저장된 값인 주소값을 복사하는 것이 아니라 해당 참조변수가 참조하는 값을 복사하여 새로운 변수를 동적할당해주어야 한다.
//(깊은)복사 생성자
Student(const Student& copy) : num(copy.num) {
int len = strlen(copy.name) + 1;
name = new char[len];
strcpy(name, copy.name);
}
복사 생성자는 세 가지 시점에서 호출된다.
int num1 = num2;
func(num1)
return num1;
→ 반환된 값을 변수에 저장하지 않더라도, 값을 반환함과 동시에 함수가 종료되기 때문에 반환값을 사용하기 위해서는 사라지는 지역변수를 대신하여 별도로 저장하고 있어야 한다.
따라서 함수가 어떤 값을 반환하면, 별도의 메모리 공간이 할당되면서 반환값으로 초기화된다.
복사생성자는 새로운 객체를 만들면서, 동시에 동일한 클래스의 객체로 초기화해야 할 때 호출된다.
⇒ 이러한 세 가지 상황은 01 단원에서 앞서 설명한 상황을 객체를 대상으로 진행한 것이라고 생각할 수 있다.
SimpleClass &ref = func(100);