프로그래밍 지식/C++

C++문법/ 생성자와 소멸자 , Constructor & Destructor

게임이 더 좋아 2021. 11. 24. 16:15
반응형
728x170

생성자란 객체가 생성되면 자동으로 호출되는 것으로 

생성자가 정의되어 있다면 해당 생성자가 호출된다.

**생성자를 호출해도 생성자는 Return이 없다. 그냥 값 초기화를 위해 쓰기 때문이다.

**생성자는 리턴값이 없더라고 void형으로 선언하지 않는다. -> 생성자에는 데이터형을 선언하지 않는다.

// 객체를 초기화 하는 역할을 하기 때문에 리턴값이 없다!
/* 클래스 이름 */ (/* 인자 */int x, int y ...) {
    x_ = x;
    y_ = y;
    ....
    ....
}
**// 언더바 _는 멤버 변수에 붙여서 구분한다. -> 멤버라는 뜻에 m_x 라고도 쓴다.

 

어..? 근데 나는 그냥 객체에 인자를 넣어서 초기화를 안해도 되던데??

Car c = new Car();

//?? 이래도 오류 안나던데??

 

생성자가 따로 정의되어 있지 않더라도 Default 생성자가 호출이 된다.

클래스에서 사용자가 어떠한 생성자도 명시적으로 정의하지 않았을 경우에

컴파일러가 자동으로 추가해주는 생성자다.

그래서 오류는 생기지 않지만 자동으로 멤버변수들을 초기화하거나 그러지는 않는다.

** 선언 후 다른 곳에서 꼭 초기화 시켜줘야 한다.

때문에 후에 객체 선언후 초기화를 따로 하지 않는다면

연산 과정에서 Junk data가 들어가서 오류가 생길것임이 분명하다.

 

그래서 사용자가 어차피 값 초기화를 따로 해줄지.. 실수로 안적었는지 C++은 모르기에

다행히 C++ 11 부터 명시적으로 디폴트 생성자를 사용하도록 명시할 수 있다.

-> C++ 11 부터는 default 생성자로 하면 멤버변수들을 초기화 시켜줌

 

즉, 사용자가 정말 컴파일러가 만드는 생성자를 디폴트로 만들고 싶었다면

아무것도 쓰지 않는 대신에 default라고 선언하는 것이다.

class Test {
 public:
  Test() = default;  // 디폴트 생성자 컴파일러가 자동으로 만들어라
};
 

**참고로 매개변수가 아무것도 없는 생성자에 대해서도 오버로딩을 하는 경우가 있다.

-> 즉, 초기화 값이 없어도 함수 오버로딩을 통해 임시 매개변수로 초기화할 수도 있고

그냥 디폴트 생성자를 하나 더 만드는 경우도 있다는 말이다.

 

예를 들어 

Abc( )
{
    a = 1;
    b = 2;
    c = 4;
}

 

21.12.05 업데이트

 

생성자 호출 방법 2가지

 

1. 명시적

<class name> name = <class name>(...)

2. 암시적

<class name> name (...)

 

 

 

 


 

 

delete obj; // 객체를 삭제하면

위에서 객체는 사라졌지만 객체 안에서 무엇인가 한 작업들이 자동으로 되돌려지지는 않는다.

따라서 한 일들에 대해서 정리를 해야 하는데 그것이 바로 소멸자이다.

 

소멸자는 객체가 소멸될 때 자동으로 호출되며, 객체를 삭제함은 물론 관련된 것도 처리한다.

다만 명시적으로 호출할 수 있는 메서드가 아니라서 인자를 전달할 수는 없다.

**우리가 소멸자를 만들지 않으면 default 소멸자가 호출된다.

 

소멸자가 자동으로 실행되는 이유는

우리가 delete 같이 명시적으로 할당된 메모리를 해제해주지 않으면 Memory Leak가 발생해서 그렇다.

 

소멸자의 포맷은 아래와 같다.

~(클래스의 이름){
	// 객체 자기 자신을 삭제시키는 것은 물론
    // 클래스 안에서 할당한 메모리를 반환같은 것을 한다.
    //더해서 스레드 lock을 풀거나 하는 역할도 할 수 있다.
}

 

**일반적으로 소멸자는 사용자가 임의로 부르는 경우가 없다.

 


 

사용 예시

 

 

class Person {
  char c;

 public:
 // -> 사용자 정의 호출자
  Person(char _c) {
    c = _c;
    std::cout << "생성자 호출 " << c << std::endl;
  }
  // -> 소멸자
  ~Person() { std::cout << "소멸자 호출 " << c << std::endl; }
};

/////////////////////

Person p = new Person(); // -> 생성자 호출

delete p; // -> 소멸자 호출

 

 


 

추가 내용

클래스 상속 시에, 소멸자를 가상함수로 만들어야 된다는 것을 알고 있어야 한다.

 

예를 들어 설명하자면

#include <iostream>

//간단하게 생성자 소멸자만 만든 클래스
class Parent {
 public:
  Parent() { std::cout << "Parent 생성자 호출" << std::endl; }
  ~Parent() { std::cout << "Parent 소멸자 호출" << std::endl; }
};

//Parent를 상속받은 Child 클래스
class Child : public Parent {
 public:
  Child() : Parent() { std::cout << "Child 생성자 호출" << std::endl; } //생성될 때 parent를 생성하고 하고 출력
  ~Child() { std::cout << "Child 소멸자 호출" << std::endl; }
};


int main() {
  std::cout << "--- 평범한 Child 만들었을 때 ---" << std::endl;
  { Child c; } // -> 대괄호를 나가면 소멸됨
  std::cout << "--- Parent 포인터로 Child 가리켰을 때 ---" << std::endl;
  {
    Parent *p = new Child();
    delete p;
  }
}

 

결과는 아래 같이 나온다.

--- 평범한 Child 만들었을 때 ---
Parent 생성자 호출
Child 생성자 호출
Child 소멸자 호출
Parent 소멸자 호출
--- Parent 포인터로 Child 가리켰을 때 ---
Parent 생성자 호출
Child 생성자 호출
Parent 소멸자 호출

밑에 거 조금 이상한데?

??? Child의 생성자만 호출되고 소멸자는 어디갔지?

 

delete p 를 하더라도, p 가 가리키는 것은 Parent 객체가 아닌 Child 객체이기 때문이다.

위에서 보통의 Child 객체가 소멸되는 것과 같은 순서로 생성자와 소멸자들이 호출되어야만 한다.

그런데 실제로는 Child 소멸자가 호출되지 않는다....?

이렇게 소멸되지 않으면 메모리 누수(memory leak)가 생기는 경우가 있다.

이것을 해결하기 위해서는 단순히 Parent 의 소멸자를 virtual 로 만들어버리면 된다.

Parent 의 소멸자를 virtual 로 만들면

p 가 소멸자를 호출할 때, Child 의 소멸자를 성공적으로 호출할 수 있게 된다.

 

위의 클래스를 약간 수정하자면

class Parent {
 public:
  Parent() { std::cout << "Parent 생성자 호출" << std::endl; }
  virtual ~Parent() { std::cout << "Parent 소멸자 호출" << std::endl; }
};
class Child : public Parent {
 public:
  Child() : Parent() { std::cout << "Child 생성자 호출" << std::endl; }
  ~Child() { std::cout << "Child 소멸자 호출" << std::endl; }
};

 

그렇게 하면 결과도 잘 나온다.

--- 평범한 Child 만들었을 때 ---
Parent 생성자 호출
Child 생성자 호출
Child 소멸자 호출
Parent 소멸자 호출
--- Parent 포인터로 Child 가리켰을 때 ---
Parent 생성자 호출
Child 생성자 호출
Child 소멸자 호출
Parent 소멸자 호출

 

여기서 한 가지 질문을 하자면, 그렇다면 왜 Parent 소멸자는 호출이 되었는가??

분명.. child 생성할 때 parent 생성자도 호출하니까 부른다고 치자.

삭제할 때는 child를 삭제하니까 child도 생성,삭제가 된다고 치는데.. 난 parent를 삭제한 적은 없는데???

 

이는 Child 소멸자를 호출하면서, Child 소멸자가 자동으로 Parent 의 소멸자도 호출해주기 때문이다

(Child 는 자신이 Parent 를 상속받는다는 것을 알고 있어서 그렇다.)

 

++ 반면에 Parent 소멸자를 먼저 호출하게 되면, Parent 는 Child 가 있는지 없는지 모른다.

그래서 Child 소멸자를 호출해줄 수 없다. (Parent 혼자서는 누구에게 상속했는지 알 수 없다.)

 

상속될 여지가 있는 기반 클래스들은 반드시 소멸자를 virtual 로 만들어주어야 나중에 문제가 발생할 여지가 없다. (Memory Leak 같은)

 


참고링크

https://modoocode.com/211

 

반응형
그리드형