복사 생성자, 언제 쓰일까?
- 새로운 객체를 만들 때: 기존 객체와 똑같은 내용을 가진 새로운 객체를 만들고 싶을 때 사용합니다.
- 객체를 함수에 전달할 때: 함수에 객체를 값으로 전달하면, 복사 생성자를 통해 함수 내부에서 사용할 복사본을 만듭니다.
- 객체를 함수에서 반환할 때: 함수가 객체를 반환하면, 복사 생성자를 통해 반환할 복사본을 만듭니다.
#include <iostream>
using namespace std;
class Person
{
public:
string name; // 이름을 저장하는 멤버 변수
int age; // 나이를 저장하는 멤버 변수
// 복사 생성자: 다른 Person 객체의 데이터를 복사하여 새로운 객체를 초기화
Person(const Person& other)
{
name = other.name; // 다른 객체의 name 값을 복사
age = other.age; // 다른 객체의 age 값을 복사
}
// 매개변수가 있는 생성자 (추가)
Person(string myname, int myage)
{
name = myname;
age = myage;
}
};
int main()
{
// 매개변수가 있는 생성자를 사용하여 Person 객체 생성
Person original("Alice", 25);
// 복사 생성자를 사용하여 새로운 객체 copy를 original로부터 생성
Person copy(original);
// 복사된 객체의 이름과 나이 출력 (Alice 25 출력)
cout << copy.name << " " << copy.age << endl;
return 0;
}
//출력
Alice 25
얕은 복사& 깊은 복사
- 얕은 복사: 간단하고 빠르지만, 두 객체가 같은 메모리를 공유하기 때문에 한 객체에서 메모리를 변경하면 다른 객체에도 영향을 미칠 수 있어요. 특히 동적 메모리 할당을 사용하는 경우 문제가 발생할 수 있습니다.
- 깊은 복사: 두 객체가 완전히 독립적이기 때문에 안전하지만, 메모리 복사에 시간이 더 걸리고 복잡한 구현이 필요할 수 있습니다.
얕은 복사와 깊은 복사의 활용 시점
- 얕은 복사: 객체의 멤버 변수가 모두 기본 자료형(int, float 등)이거나 동적 메모리 할당을 사용하지 않는 경우에 적합합니다.
- 깊은 복사: 객체의 멤버 변수 중에 동적 메모리 할당을 사용하는 포인터가 있는 경우에는 깊은 복사를 사용해야 합니다.
Person man1("Lee dong wll", 29);
Person man2=man1; //복사 생성자 호출
복사 생성자와 동적 메모리 할당:
- 만약 클래스의 멤버 변수 중에 동적으로 할당된 메모리를 가리키는 포인터가 있다면, 얕은 복사를 사용하는 복사 생성자는 문제를 일으킬 수 있습니다.
- 얕은 복사는 포인터 값만 복사하기 때문에, 원본 객체와 복사된 객체가 같은 메모리 영역을 가리키게 됩니다. 이 경우 한 객체에서 메모리를 변경하면 다른 객체에도 영향을 미치거나, 객체가 소멸될 때 같은 메모리 영역을 두 번 해제하려고 하는 문제가 발생할 수 있습니다.
깊은 복사
- 독립적인 메모리 공간 확보
- 이러한 문제를 해결하기 위해 깊은 복사를 사용해야 합니다. 깊은 복사는 동적으로 할당된 메모리까지 복사하여 완전히 독립적인 새로운 객체를 만듭니다. 즉, 원본 객체와 복사된 객체는 각자 독립적인 메모리 공간을 가지게 되므로, 한 객체에서 메모리를 변경해도 다른 객체에 영향을 미치지 않습니다.
깊은 복사를 구현하려면?
- 깊은 복사를 구현하려면 복사 생성자 내부에서 new 연산자를 사용하여 새로운 메모리 공간을 할당하고, 기존 객체의 데이터를 복사해야 합니다.
#include <iostream>
using namespace std;
// SoSimple 클래스 정의
class SoSimple
{
private:
int num; // 멤버 변수 num
public:
// 생성자: 매개변수 n을 받아 num을 초기화
SoSimple(int n) : num(n)
{ }
// 복사 생성자: 객체가 복사될 때 호출됨
SoSimple(const SoSimple& copy) : num(copy.num)
{
// 복사 생성자가 호출될 때 출력
cout << "Called SoSimple(const SoSimple& copy)" << endl;
}
// AddNum 함수: num 값에 n을 더하고, 자기 자신을 반환 (메서드 체이닝 가능)
SoSimple& AddNum(int n)
{
num += n; // num에 n을 더함
return *this; // 자기 자신을 반환
}
// ShowData 함수: num 값을 출력하는 함수
void ShowData()
{
cout << "num: " << num << endl; // num 값 출력
}
};
// SoSimple 객체를 매개변수로 받아서 처리하는 함수
SoSimple SimpleFuncObj(SoSimple ob) // 값으로 전달됨 (복사본이 생성됨)
{
cout << "return 이전" << endl; // 반환 이전에 출력
return ob; // 매개변수로 받은 ob 객체를 반환 (여기서 복사된 객체 반환)
}
int main()
{
// SoSimple 객체 생성, num을 7로 초기화
SoSimple obj(7);
cout << "함수호출 전" << endl; // 함수 호출 전 출력
// SimpleFuncObj 함수 호출: obj의 복사본이 생성됨
// 반환된 복사본에 대해 AddNum(30)을 호출하여 num에 30을 더하고 ShowData로 출력
SimpleFuncObj(obj).AddNum(30).ShowData(); // obj의 복사본에서 num 값이 37이 됨
// 원래 obj 객체의 num 값을 출력 (함수 호출로 인해 변화 없음)
obj.ShowData(); // 원래 obj의 num 값은 여전히 7임
return 0;
}
'C++' 카테고리의 다른 글
this 포인터의 이해 (0) | 2024.09.26 |
---|---|
' : '과 ' :: '의 차이점 (0) | 2024.09.26 |
c++클래스(2) (0) | 2024.09.25 |
c++ 참조 (0) | 2024.09.25 |
c++ 클래스 (3) | 2024.09.25 |
댓글