일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- GeeksForGeeks
- 언리얼엔진5
- softeer
- winapi
- C++
- RVO
- UnrealEngine4
- 오블완
- 백준
- 2294
- UE5
- Unreal Engine5
- baekjoon
- 프로그래머스
- algorithm
- C
- UnrealEngine5
- NRVO
- 티스토리챌린지
- IFileDialog
- Frustum
- 1563
- DeferredRendering
- 줄 세우기
- 팰린드롬 만들기
- directx
- Programmers
- DirectX11
- RootMotion
- const
- Today
- Total
Game Develop
[C++] 복사생성자 , 얇은복사 깊은복사 본문
복사생성자란
C++에서 복사 생성자란 자신과 같은 클래스 타입의 다른 객체에 대한 참조(reference)를 인수로 전달받아, 그 참조를 가지고 자신을 초기화하는 방법이다.
복사생성자를 설명하면서 얕은복사와 깊은복사에 관해서도 간단하게 설명한다.
기본 복사 생성자
- 기본적으로 제공되는 생성자. 따로 정의 안해도 객체간의 변수의 값을 얕은복사(shallow copy)로 수행한다
- 객체를 선언함과 동시에 초기화할 때 수행된다.
Ex) Object a;
Object b = a; // 복사생성자 수행. b를 선언과 동시에 a의 값으로 초기화했기 때문.
Object c(b); // 복사생성자 수행. 선언과 동시에 b의 값으로 초기화함.
이렇게 따로 사용자가 정의하지 않아도 기본적인 복사생성자는 수행된다.
하지만 이 기본복사생성자들은 얕은복사로 수행된다. 얕은복사는 그냥 그대로 변수의 값들을 복사해오는 것인데,
만약 객체의 멤버변수에 포인터변수(주소를 담는 변수)가 있을 경우 문제가 된다.
class Object
{
Object() // 기본생성자.
{
int a = 10;
int *b = new int[1];
}
~Object()
{
delete b;
}
private:
int num;
int* p;
}
위와 같은 클래스가 있다. 그리고 아래의 예제를 살펴보자.
Object a; // 기본생성자 수행.
Object b = a; // 복사생성자 수행.
여기서 a는 기본생성자가 수행되며 멤버변수의 값들이 초기화 됐을것이다.
그다음 Object b = a; 가 수행되면서 인스턴스 b의 멤버변수들은 a의 멤버변수의 값을 가지고 초기화되며 생성되었을것이다.
여기서 문제는 인스턴스 a와 b의 멤버변수 p가 같은 주소값을 가지고있다는 것이다.
a가 생성되면서 포인터변수 a.p(a의 멤버변수 p라는 의미)는 new연산자로 힙메모리에서 공간을 할당받고 그 공간을 가리키는 주소값을 받아왔을 것이다.
그리고 기본 복사생성자는 값을 그대로 복사하기 때문에 b.p는 a.p에 담겨있는 주소값 그대로 복사해서 가져왔을것이다.
즉, a.p와 b.p는 같은 힙메모리주소값을 가리키고 있다.
인스턴스는 서로 독립되어야하기 때문에 이와 같은 경우는 많은 문제를 야기할 것이다.
발생할 문제점 중 하나로 메모리 해제시의 문제점을 예시로 들 수 있다.
프로그램 종료 시, a와 b 둘 다 소멸자가 실행될텐데 Object 클래스의 소멸자는 멤버포인터변수 p에 대해 delete(동적할당 해제)연산자를 수행하게된다.
그런데 a와 b가 각각 갖고 있는 멤버포인터변수 p는 같은 힙메모리의 주소를 가리키고있다.
같은 힙메모리의 주소에 대해 2번의 delete연산이 수행되기 때문에 문제가 발생하게 된다.
실제로 위의 코드를 그대로 복사해서 실행할 경우 동적메모리관련한 에러가 바로 뜬다.
그래서 포인터변수가 멤버변수에 포함되어있을 경우, 아래와 같이 개발자가 직접 깊은복사생성자를 정의해줘야한다.
class Object
{
public:
Object()
{
a = 10;
b = new int;
*b = 20;
}
Object(const Object &o) // 복사생성자 정의할 시, 매개변수는 레퍼런스형태로 선언해줘야 한다.
{
a = o.a;
b = new int; // 따로 동적할당을 해 준 후에,
*b = *(o.b); // o.b의 힙메모리주소안의 내용물만 꺼내서 복사해온다.
// 각 인스턴스의 포인터변수는 따로 new연산을 했기 때문에 문제가 발생하지 않는다.
}
~Object() { delete b; }
private:
int a;
int* b;
};
그리고 복사생성자를 정의할 때 매개변수 레퍼런스형태로 해줘야하는 이유는 무한루프를 방지하기 위해서이다.
만약에 레퍼런스형태가 아니라 Object(const Object o)로 정의해줬다고 가정하자.
그러면 스택메모리에 임시객체가 만들어지고 o에 복사가 되어질텐데 그 과정에서 또 복사생성자가 수행된다.
그리고 같은 과정이 계속 반복하게 되면서 무한루프에 빠지게 된다.
'C++ > C++' 카테고리의 다른 글
[C++] 이동생성자. (0) | 2021.07.12 |
---|---|
[C++] 대입연산자 (0) | 2021.05.01 |
[C++] C++ 코딩 스탠다드 참고하는 사이트. (0) | 2021.04.08 |
[C++] 싱글톤 사용시 주의점 (0) | 2021.04.01 |
[C++] 싱글톤(Singleton)에 대하여 (0) | 2020.10.18 |