티스토리 뷰

Programming/C++

[c++]소멸자함수

쩨리쩨리 2018. 3. 7. 20:22
반응형

* class 구조
1. 생성자함수
2. 복사생성자함수
3. 소멸자 함수
4. 대입연산자 함수

 

 

 

* 소멸자

c++은 java나 c#과 달리 플랫폼에서 개체들을 관리하지 않는다. 따라서 c++에서는 생성한 개체의 소멸에 관한 책임을 개발자가 져야한다.

소멸자도 생성자처럼 직접 호출하지 않고 delete 연산자를 사용해야한다. 또한 소멸자는 생성자와 다르게 중복 정의할 수 없다. 소멸자도 개발자가 정의하지 않으면 컴파일러에 의해 디폴트 소멸자가 만들어진다. 하지만 개체 내부에서 동적으로 다른 개체를 생성하였다면 소멸자를 정의하여 동적으로 만든 개체를 소멸해야 한다.

 

 

 

* 소멸자함수를 쓰는 이유

동적메모리를 할당 하고 난뒤 쓰레기값이 남게된다. 프로세스를 다 돌리고 난 후 쓰레기 메모리 값을 소멸해주지 않으면 다른 프로그램을 프로세스 할 때마다 메모리들이 남아 메모리 공간을 차지하게 된다. 소멸자 함수를 쓰는 이유는 메모리 낭비를 줄이기 위해서라고 할 수 있다.

 

 

 

* 소멸자 함수 특징
1. 객체 소멸시 자동 호출되어지는 함수. 만약, 동적 할당뒤에 메모리 할당 해제 안 해준 경우 소멸자 함수 호출 불가능
2. 생김새 : 틸드(~)가 클래스명 앞에 있으면 소멸자 함수

 

~클래스명(   ){        }


 

3. 매개변수를 선언 할 수 없다.
4. 오버로딩이 불가능하다. 매개변수가 없기 때문에 클래스 통틀어서 함수가 오직 하나이다.
5. const member function으로 만들수 없다.
6. 객체의 잔여메모리를 깨끗하게 정리하는 역할을 함(개발자가 직접 설정해줘야함)

7. 역할 : 동적 메모리 필드 제거

 

 

 

* 소멸자 함수 이해하기

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <iomanip>
using namespace std;
 
 
class A {
 
    int *p;
 
public:
 
    //생성자
    A(int i = 0) {
        p = new int;//동적 메모리 할당
        *= i;//포인터가 잡고 있는 주소에 i의 값이 들어간다
    }
 
 
    //복사생성자가 생략되어 있음
    /*A(const A&aa) {
        p = aa.p;
        //포인터의 주소값 복사
    }*/
 
 
    ~A() {
        delete p;//메모리 모두 소멸시키기
    }
 
 
    int getP() { return *p; }
 
};
 
 
void main() {
 
    A aa(10);
    A bb(aa);//복사생성자 호출
    
    cout << bb.getP() << endl;
    //main은 stack 영역에 저장된다. 
    //스택은 마지막에 들어온 것이 제일 처음 사라진다(후입선출,LIFO)
    //결국 bb, aa순으로 사라짐
    //이때, bb와 aa는 똑같은 10의 주소를 가리키고 있는데, bb가 가리키는 10이 사라져서
    //aa 메모리를 삭제할 수 없어진다. >>런타임 오류가 뜬다.
 
}
cs

 

 

 

 

* 위의 소멸자 함수 오류코드를 고쳐보자

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <iomanip>
using namespace std;
 
 
 
class A {
 
    int *p;
 
public:
 
    //생성자
    A(int i = 0) {
        p = new int;//aa 포인터
        *= i;//포인터가 가리키는 주소에 i의 값을 넣어라
    }
 
    //복사생성자//깊은 복사
    A(const A&aa) {
        p = new int;//bb 포인터
        *= *aa.p;//포인터가 가리키는 값에 매개변수 aa  넣어라
        
    }
 
    //소멸자 함수
    ~A() {
        delete p;//메모리 모두 소멸시키기
    }
 
    int getP() { return *p; }
 
};
 
 
void main() {
 
    A aa(10);
    A bb(aa);//복사생성자 호출
    
    cout << bb.getP() << endl;
    //main은 stack 영역에 저장된다. 
    //스택은 후입선출(LIFO)
    //결국 bb , aa순으로 사라짐 
    //위에서는, bb와 aa는 똑같은 10을 넣은 주소를 가리키고 있어서 런타임오류가 떴었는데
    //그 오류를 안 뜨게 하기위해선 bb의 포인터가 주소를 가리키는 것이 아닌
    //그 주소에 들어있는 값을 가리키게 하면 된다.
 
}    
cs

 

 

 

 

 

* 소멸자 함수 응용하기

1. 다음 코드를 보고 출력되는 문구가 총 몇줄인지 맞춰보시오.

2. 어떤 문구가 순서대로 출력 되는지 맞춰보시오.

 

 

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
#include <iostream>
#include <iomanip>
using namespace std;
 
class A {
 
public :
    A() { cout << "생성자 함수" << endl; }
    ~A() { cout << "소멸자 함수" << endl; }
    A(const A&aa) { cout << "복사생성자함수" << endl; }
    void setObject(A aa) {}
    A getObject() { A aa; return aa; }
 
};
 
void main() {
 
    A aa;//1번
    A bb(aa);//2번
    A cc = bb;//3번
 
    aa.setObject(bb);//4번
    bb.setObject(cc);//5번
    cc.setObject(aa);//6번
 
    aa.getObject();//7번
    bb.getObject();//8번
    cc.getObject();//9
 
}
cs

 

 

 

 

 

 

* 풀이

1번 : aa 객체 선언, 생성자로 감, main(stack 영역에 저장됨)이 끝날때 까지 객체가 존재하기에 소멸자함수는 main이 끝난다음 호출됨

2번 : bb 객체 선언, 복사 생성자로 감, main이 끝난 다음 소멸자 함수 호출됨

3번 : cc 객체 선언, 복사 생성자로 감, main이 끝난 다음 소멸자 함수 호출됨

4번, 5번, 6번 : bb를 인자로 갖는 aa 객체, 복사 생성자 호출, bb인자는 매개변수 aa가 받음. class 영역 에서 함수 기능이 종료됐기 때문에 set 함수 종료뒤 소멸자 함수 호출

7번, 8번, 9번 : get 함수에서 맨 처음 aa 객체 선언(이때 main의 aa 객체와는 구별된다)하여 생성자함수 호출, 메소드에서 return을 하면 무조건 복사생성자를 호출한다. get 함수가 끝나면 생성자 함수, 복사생성자 함수 2가지 기능이 끝난 것이기에 소멸함수도 2번 호출 된다.

1번, 2번, 3번 : main 함수가 종료되면 1번, 2번, 3번 객체선언한 기능이 사라지므로 소멸자 함수 3번 호출

 

 

 

* 출력 결과

반응형
댓글
공지사항