생성자
생성하자마자 초기화를 해준다. 객체 생성시 자동으로 호출된다.
디폴트 생성자
class Date{
~~~
~~~
}
int main(void)
{
Date day;
}
Date의 생성자를 명시하지 않아도, 생성자를 클래스내에 정의하지 않아도, 기본 생성자
가 호출된다. → 컴파일러가 자동으로 추가해줌
생성자 오버로딩
생성자로 인자를 다르게 해서, 생성시 초기화할때 어떤 생성자를 사용할 것인지 선택할 수 있다.
Date day; //기본 생성자 호출
Date day2 = Date(); //기본 생성자 호출
Date day3(1, 11, 111); //사용자 정의 생성자 호출
Date day4 = Date(2, 22, 222); //사용자 정의 생성자 호출
Date day2(); //XXX 이렇게 하면 안됨.
동적할당시 new
new
연산자는 객체를 동적으로 생성하면서, 동시에 자동으로 생성자로 호출해준다.
class Marine{
~~~
~~~
Marine(); //기본 생성자
Marine(int x, int y); //사용자 정의 생성자
}
int main(void)
{
Marine* marine_arr[100];
marine_arr[0] = new Marine(2, 3); //객체를 동적으로 생성하면서
marine_arr[1] = new Marine(5, 6); //동시에 생성자 자동호출
~~
~~
delete marine_arr[0]; //할당해제
delete marine_arr[1];
}
소멸자
객체가 소멸할 때, 자동으로 호출되는 함수.
class Marine{
~~~
~~~
Marine(); //기본 생성자
Marine(int x, int y); //사용자 정의 생성자
~Marine(); //소멸자 (소멸자는 인자를 가지지 않는다.)
}
int main(void)
{
Marine* marine_arr[100];
marine_arr[0] = new Marine(2, 3); //객체를 동적으로 생성하면서
marine_arr[1] = new Marine(5, 6); //동시에 생성자 자동호출
~~
~~
delete marine_arr[0]; //할당해제
delete marine_arr[1];
}
delete
를 하면 객체만 사라진다. 만약에, 객체에서 또다른 메모리를 new
로 할당했다면, 객체가 delete되어도 이 또 다른 메모리는 살아있다.
→ Memory Leak
이 생긴다.
따라서 소멸자에서 이 또 다른 메모리를 해제시켜주어야한다.
복사 생성자
객체를 1개만 생성해 놓고, 그 한개를 가지고 나머지 객체들을 복사 생성
한다.
정의
어떤 클래스 T가 있다면T(const T& a)
로 정의한다.
다른 T의 객체 a를 상수 레퍼런스로 받는다.
또한, a가 const형식으로 a의 값들을 복사할수 있지만, 변경할 순 없다.
class Snack{
~~
~~
Snack(int cal, int price);
}
Snack::Snack(const Snack& sn)
{
cal = sn.cal;
price = sn.price;
}
Snack sn1(330, 2000); //330칼로리에 가격은 2000원인 객체 생성
Snack sn2 = sn1; //복사 생성자 사용
//혹은
Snack sn2(sn1); //이렇게도 가능
복사 생성자는 오직 ‘객체 생성’시에만 호출된다.
컴파일러는 이미 디폴트 복사 생성자를 지원해준다.
BUT,
디폴트 복사 생성자의 한계
만약, 복사 생성자의 인자에 포인터가 있다면, 문제가 된다.
class Snack
{
int cal;
int price;
char* name;
~~
~~
Snack(int cal, int price, char* snack_name);
~Snack();
}
Snack::Snack(int cal, int price, char* snack_name)
{
this.cal = cal;
this.price = price;
this.snack_name = snake_name;
}
~~
~~
Snack sn1 = Snack(330, 2000, "potato chip");
Snack sn2 = sn1; //복사생성자를 사용해서 복사
sn1의 과자 이름은 “potato chip”이다.
Snack sn2 = sn1;
문장을 실행할 때, 컴파일러는 자동으로 기본 복사 생성자를 만들어서 객체의 요소를 하나하나 복사한다.
this.cal = sn1.cal;
this.price = sn1.price;
this.name = sn1.name;
이런식으로 복사를 하는데, 문제는 char*형인 name은 포인터라는 것이다.
그러면, sn1.name과 sn2.name은 같은 메모리 공간을 가리키게된다.
같은 메모리 공간을 가리키는건 문제가 되지 않지만, 문제는 소멸자에서 발생한다.
sn1이 소멸하면 자동으로 sn1.name포인터는 메모리 공간에서 사라지게되고, sn2가 소멸할때는 이미 소멸되고 없는 sn2.name을 소멸시키려고 하기 때문이다. → 한번 해제된 메모리를 접근하여 또 해제하려고함!
해결법
포인터 자체를 복사하지말고, 다른 부분에 동적할당을 해서, 그 내용만 복사하면 될 것이다.
Snack::Snack(const Snack& sn)
{
this.cal = sn.cal;
this.price = sn.price;
this.name = new char[strlen(sn.name) + 1]; //동적할당해서
strcpy(this.name, pc.name); //내용을 복사한다.
}
얕은 복사(shallow copy), 깊은 복사(deep copy)
문제가 생겼던 단순한 복사 방식을 shallow copy
해결한 복사 방식을 deep copy
라고 한다.
'C++ > 개인 공부' 카테고리의 다른 글
C++/참조자(레퍼런스) (0) | 2025.02.01 |
---|---|
가상 함수와 오버라이딩 (0) | 2023.06.07 |