
[C++] 복사 생성자, 깊은 복사와 얕은 복사
sangjun
·2021. 4. 14. 16:04
C++의 초기화 방법
C++에서는
int num=20;
int &ref=num;과
int num(20);
int &ref(num);으로 두 가지 초기화 방법이 있고 두 가지 결과 또한 동일합니다.
하지만 이것은 변수와 변수간의 초기화 방법일 뿐이고
객체와 객체간의 초기화 방법은 약간 다른 개념이 추가되어야 합니다.
왜냐하면 객체에는 수많은 복잡한 내용들이 포함되어 있기 때문에 일반 변수와는 차별성을 두어야 합니다.
C++에서의 변수와 객체 초기화 소개에 앞서 아래 코드를 먼저 살펴보겠습니다.
#include<iostream>
class SoSimple {
private:
int num1;
int num2;
public:
SoSimple(int n1, int n2) : num1(n1), num2(n2)
{}
void ShowSimpleData()
{
std::cout << num1 << std::endl;
std::cout << num2 << std::endl;
}
};
int main()
{
SoSimple sim1(15, 20);
SoSimple sim2 = sim1;
sim2.ShowSimpleData();
return 0;
}
SoSimple생성자에서 멤버변수의 초기화가 이루어지고 있습니다.
즉, sim1객체의 num1과 num2는 각각 15와 20으로 초기화가 이루어진다는 것입니다.
하지만 메소드는 sim2.ShowSimpleData()로 sim2의 멤버변수들을 출력하고 있습니다.
출력결과는 15와 20이 출력됩니다. 그 이유를 이제 알아보도록 하겠습니다.
SoSimple sim2=sim1 문장은 디폴트 생성자라는 것에 의해서 sim1과 sim2의 멤버와 멤버 간의 복사가 이루어집니다.
디폴트 생성자란
디폴트 생성자란 SoSimple생성자의 인자인 int형 정수 2개가 아닌 객체가 인자로 넘어왔을 때
컴파일러에 의해서 자동적으로 생성되는 생성자입니다.
아래 예제를 통해서 객체를 초기화 할 때 생성자의 인자로 기본 자료형을 넘겨줄 때와 객체를 넘겨줄 떄의 차이점을
확인해보겠습니다.
#include <iostream>
using namespace std;
class SoSimple {
private:
int num1;
int num2;
public:
SoSimple(int n1, int n2) :num1(n1), num2(n2) {
}
SoSimple(SoSimple& copy):num1(copy.num1),num2(copy.num2)
{
cout << "Called SoSimple(soSimple ©)" << endl;
}
void ShowSimpleData()
{
cout << num1 << endl;
cout << num2 << endl;
}
};
int main()
{
SoSimple sim1(15, 30);
cout << "생성 및 초기화 직전" << endl;
SoSimple sim2 = sim1;
cout << "생성 및 초기화 직후" << endl;
sim2.ShowSimpleData();
return 0;
}
출력결과를 살펴본다면 SoSimple sim2=sim1을 호출할 때는 일반 생성자를 호출하지 않고
디폴트 생성자를 호출하는 것을 볼 수 있다.
깊은 복사와 얕은 복사
깊은 복사와 얕은 복사의 차이점은 포인터 관점에서 보면 이해가 잘 될 것이다.
위와 같이 디폴트 생성자를 사용할 경우 얕은 복사가 이루어져
두 객체의 멤버변수가 똑같은 메모리 주소를 가르키는 경우
delete키워드를 사용할 때 Double Free Bugs가 발생할 수 있다.
아래는 Double Free Bugs가 발생하는 예제이다.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;
class Person
{
private:
char* name;
int age;
public:
Person(const char* myname, int myage)
{
int len = strlen(name) + 1;
name = new char[len];
strcpy(name, myname);
age = myage;
}
void ShowPersonInfo() const
{
cout << "이름: " << name << endl;
cout << "나이: " << age << endl;
}
~Person()
{
delete []name;
cout << "called destructor!" << endl;
}
};
int main()
{
Person man1("Lee dong woo", 29);
Person man2 = man1;
man1.ShowPersonInfo();
man2.ShowPersonInfo();
return 0;
}
위의 예제를 실행해보면 소멸자가 두 번 실행되어야 하는데 한번만 실행되는 것을 볼 수 있을 것이다.
그럼 깊은 복사를 하기 위한 방법을 알아보자.
Person(const Person ©):age(copy.age)
{
name=new char[strlen(copy.name)+1];
strcpy(name, copy.name);
}
방법 : 생성자 오버로딩을 통해 인자를 객체의 형태로 받게 함으로써 똑같은 주소를 가리키지 않게 할 수 있다.
즉, 문자열을 위한 메모리 공간을 따로 할당 받게 된다.
'프로그래밍' 카테고리의 다른 글
[Network] NAT란 (0) | 2021.07.08 |
---|---|
[ncurses] linux ui library (0) | 2021.05.16 |
[JAVA] 접근 지정자 (0) | 2021.05.15 |
[C++] 복사 생성자, 깊은 복사와 얕은 복사 (0) | 2021.04.14 |
[자료구조] 스택과 큐 (0) | 2021.04.12 |
[JAVA] Scanner Class 사용법 (1) | 2021.04.10 |
0개의 댓글