디폴트 복사 생성자는 멤버 대 멤버를 그대로 복사한다. 이러한 방식의 복사를 얕은 복사(shallow copy)라고 하는데 이는 멤버변수가 힙(Heap)의 메모리 공간을 참조하는 경우에 문제가 발생한다.
#include <iostream>
#include <cstring>
class Person
{
char* name;
int age;
public:
Person(char* myname, int myage)
{
int len = strlen(myname) + 1;
name = new char[len];
strcpy(name, myname);
age = myage;
}
void ShowPersonInfo() const
{
std::cout << "이름 : " << name << std::endl;
std::cout << "나이 : " << age << std::endl;
}
~Person()
{
delete[]name;
std::cout << "called destructor" << std::endl;
}
};
int main(void)
{
Person p1((char*)"Park", 4); //p1 객체를 생성하고 초기화, 이때는 생성자가 호출
Person p2(p1); // p2 객체를 생성하고, p1 객체로 초기화, 이때 복사 생성자가 호출
p1.ShowPersonInfo();
p2.ShowPersonInfo();
return 1;
}
위 코드에서 객체 p1
과 p2
가 생성되는 과정을 자세히 살펴보자.
먼저, p1
객체가 생성될 때, 포인터 변수 name
은 myname
의 길이만큼 메모리를 할당받고 해당 메모리 영역을 참조하게 된다. 예를 들어, "Park "이라는 문자열이 저장된 메모리 주소 0x12F
번지를 참조하고 있을 것이다.
p2
객체를 p1
으로 초기화하면 복사 생성자가 호출된다. 이 과정에서 p1
의 name
과 age
값이 그대로 p2
의 name
과 age
에 복사된다.
이렇게 되면 p1
과 p2
의 name
포인터는 모두 동일한 메모리 영역(예: 0x12F
)을 참조하게 된다. 여기까지는 문제가 없지만, 객체가 소멸될 때 문제가 발생한다.
p1
이 먼저 소멸되고 p2
가 소멸되는 시나리오를 살펴보자. p1
의 소멸자가 호출되면 name
이 참조하고 있는 메모리 영역의 할당이 해제된다. 따라서 p2
의 소멸자가 호출될 때, p2
의 name
포인터는 이미 해제된 메모리 영역을 참조하고 있어 런타임 에러가 발생하게 된다.
이 문제를 해결하려면 복사 생성자를 명시적으로 정의하여 name
포인터의 깊은 복사를 수행해야 한다. 이렇게 하면 각 객체가 독립적인 메모리 영역을 참조하게 되어, 소멸 시 문제가 발생하지 않는다.
#include <iostream>
#include <cstring>
class Person
{
char* name;
int age;
public:
Person(char* myname, int myage)
{
int len = strlen(myname) + 1;
name = new char[len];
strcpy(name, myname);
age = myage;
}
// 깊은 복사를 위한 복사생성자 정의
Person(const Person& p) : age(p.age)
{
name = new char[strlen(p.name)+1];
strcpy(name, p.name);
}
void ShowPersonInfo() const
{
std::cout << "이름 : " << name << std::endl;
std::cout << "나이 : " << age << std::endl;
}
~Person()
{
delete[]name;
std::cout << "called destructor" << std::endl;
}
};
int main(void)
{
Person p1((char*)"Park", 4); //p1 객체를 생성하고 초기화, 이때는 생성자가 호출
Person p2(p1); // p2 객체를 생성하고, p1 객체로 초기화, 이때 복사 생성자가 호출
p1.ShowPersonInfo();
p2.ShowPersonInfo();
return 1;
}
p1이 생성되면서 생성자에 의해 p1.name
에는 myname
의 길이만큼 메모리를 할당하게 된다. p2를 선언하면서 전달인자로 p1을 전달했기 때문에 Person Class의 복사 생성자가 호출된다. 복사 생성자의 매개변수는 p1 객체를 참조하고 있다. 복사 생성자에서는 name
에 p1.name
의 길이만큼 메모리를 할당하고, p1.name
가 참조하는 메모리 영역에 저장된 문자열을 name
에 복사한다.
name = new char[strlen(p.name)+1];
strcpy(name, p.name);
복사 생성자에서는 새로운 메모리영역을 할당해 문자열을 복사했기 때문에 p1
객체가 소멸해도 p2.name
이 참조하는 메모리 영역에는 아무런 영향을 주지 않기 때문에 에러가 발생하지 않는다.
'Programming language > C++' 카테고리의 다른 글
[C++] 오버로딩(Overloading) (0) | 2024.09.01 |
---|---|
[C++] 함수 오버라이딩(override) (0) | 2024.08.31 |
[C++] 소멸자(Destructor), 디폴트 소멸자(Default destructor) (0) | 2024.05.03 |
[C++] new, delete, 메모리 할당, 메모리 관리 (1) | 2024.05.02 |
[C++] 생성자(constructor), 디폴트 생성자(default constructor), 생성자? 생성자 란? 생성자 오버로딩 (0) | 2024.05.01 |