📚생성자와 소멸자

변수는 선언과 정의를 통해 변수를 생성하고 사용할 수 있었습니다. 하지만 클래스는 멤버가 여러 개 존재할 수 있어서 변수 처럼 단일 표현식으로 처리가 어렵습니다. 따라서 C++ 클래스는 생성자와 소멸자라는 개념을 추가했습니다.

  • 생성자(Constructor): 클래스의 객체가 인스턴스화될 때 자동적으로 호출되는 특별한 멤버함수입니다. 생성자는 클래스의 유효한 인스턴스를 생성하는 것을 담당하여 멤버들을 적절하게 초기화하는 것을 책임집니다.

  • 클래스 생성자 규칙

    • 클래스 이름과 동일해야 합니다.(대소문자 모두 일치)
    • 반환 타입이 없습니다.(void도 아닙니다.)


  • 소멸자(Destructor): 클래스 객체가 소멸될 때 객체가 사용하던 자료를 깨끗하게 지우는 것을 담당하는 특별함 멤버함수입니다.

  • 클래스 소멸자 규칙

    • 클래스와 동일한 이름에 접두어로 ~(tilde)를 붙입니다.
    • 매개변수가 없습니다.
    • 반환 타입이 없습니다.(void도 아닙니다.)

클래스 생성자나 소멸자를 따로 작성하지 않으면 C++ 컴파일러는 기본 생성자, 소멸자를 자동으로 만들어줍니다. 클래스 인스턴스도 기본적인 변수의 라이프사이클에 따라 자동으로 생성과 소멸이 이루어지는데, 생성자와 소멸자는 그 과정에서 사용자가 직접 개입하여 기능을 작성할 수 있게 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

class MyClass
{
public:
    MyClass()
    {
        std::cout << "[Constructor]" << std::endl;
    }

    ~MyClass()
    {
        std::cout << "[Destructor]" << std::endl;
    }
};

int main()
{
    MyClass c;
    // MyClass로 인스턴스 c를 만들고 어떤 멤버함수도 부르지 않았지만
    // 실행 결과 생성자와 소멸자가 호출되는 것을 알 수 있습니다.
}




📚생성자

  • 생성자의 목적
    • 멤버 변수 초기화: 기본 값으로 초기화하거나 사용자가 넘겨준 값으로 초기화합니다.
    • 필요한 기본 작업: 자료구조를 준비하거나, 사용할 데이터 파일을 불러오는 등의 내부 작업이 필요합니다.

📄기본 생성자

기본 생성자는 매개변수가 없는 생성자의 형태입니다. 멤버들을 기본값으로 초기화하는 생성자입니다.

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
#include <iostream>

class Quest
{
private:
    int mID;
    int mExp;

public:
    Quest()
    {
      mID = 1;
      mExp = 1;
    }

    void Print()
    {
        std::cout << "QeustID: " << mID << std::endl;
        std::cout << "QeustExp: " << mExp << std::endl;
    }
};

int main()
{
    Quest q1;

    q1.Print();
}

Quest의 인스턴스 q1은 인스턴스화할 때 어떤 인자도 넘겨주지 않았습니다. 이때 자동적으로 기본 생성자 Quest()가 불려집니다.

위 코드는 다음과 같은 과정을 거칩니다.

  1. Quest 타입에 맞는 메모리 준비
  2. 메모리 공간에 이름 q1이라고 붙여 객체 준비
  3. 기본 생성자를 호출하여 초기화
  4. 생성자의 본체에서 mID = 1, mExp = 1 대입문을 통해 멤버 지정



📄생성자 형태

기본 생성자는 기본값으로만 초기화할 수 있어 사용자가 원하는 값으로 초기화할 수 없습니다. 생성자의 사용법은 함수랑 같기 때문에 매개변수가 다른 동일한 생성자도 만들 수 있습니다.

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
#include <iostream>

class Quest
{
private:
    int mID;
    int mExp;

public:
    Quest()
    {
        mID = 1;
        mExp = 1;
    }

    Quest(int id, int exp)
    {
        mID = id;
        mExp = exp;
    }

    void Print()
    {
        std::cout << "QeustID: " << mID << std::endl;
        std::cout << "QeustExp: " << mExp << std::endl;
    }
};

int main()
{
    Quest q1;
    Quest q2(3, 50);

    q1.Print();
    q2.Print();
}

반환값과 이름이 같고 매개변수가 다른 함수 오버로딩 처럼 생성자도 동일한 규칙을 적용할 수 있습니다.

생성자가 많아지면 코드가 헷갈릴 수 있기 때문에 아래처럼 간략화하는 것이 좋습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

class Quest
{
private:
    int mID;
    int mExp;

public:
    Quest(int id = 1, int exp = 1)
    {
        mID = id;
        mExp = exp;
    }

    void Print()
    {
        std::cout << "QeustID: " << mID << std::endl;
        std::cout << "QeustExp: " << mExp << std::endl;
    }
};


  • 초기화 종류

    • 복사 초기화: 배정문을 통해 값을 대입하는 방식입니다. 예) int a = 1;
    • 직접 초기화: 함수 형태로 초기화를 수행하는 것으로 생성자와 같은 방식입니다. 예) int b(1);
    • 유니폼 초기화: 복사/직접 초기화는 일부 타입에서만 작동합니다. C++11에서 모든 객체에 동일하게 작동하는 유니폼 초기화라는 개념을 고안하였습니다. 형변환이 허용되지 않아 안전하고 빠릅니다. 예) int c{1};
    • 유니폼 복사 초기화: 유니폼 초기화로 객체를 생성한 후 복사하는 방식입니다. 예) int d = {1};


클래스 초기화    
Quest q1 = Quest(); 복사 초기화 객체를 새로 만들어 배정합니다.
새로 만들어지는 객체는 기본 생성자를 통해 초기화됩니다.
Quest q2(1, 2); 직접 초기화 생성자를 직접 호출하여 초기화합니다.
Quest q3{1, 2}; 유니폼 초기화 public 변수들을 초기화하는 유니폼 초기화입니다.
생성자가 없어도 멤버들을 알아서 초기화 해줍니다.
Quest q4 = {1, 2}; 유니폼 복사 초기화 유니폼 초기화를 사용하면 적절히 Quest 객체로 만들어 줍니다.

클래스의 복사 초기화는 멤버가 많으면 느려질 수 있기 때문에 사용을 피해야합니다.


포인터 유니폼 초기화  
int* p{}; 정수를 가리키는 포인터 변수 p를 nullptr로 초기화합니다.
Quest* pQuest{}; Quest를 가리키는 포인터 변수 pQuest를 nullptr로 초기화합니다.
Quest* pQuest{
new Quest{} };
Quest를 동적으로 만든 다음 기본 생성자를 호출하여 초기화한 후 pQuest 포인터가 가리키도록 지정합니다.


  • 암시적 기본 생성자

프로그래머가 생성자를 만들지 않으면 C++ 컴파일러는 자동으로 암시적 기본 생성자를 만들어 줍니다. 암시적 기본 생성자는 편의 기능이므로 멤버 초기화는 하지 않습니다. 따라서 암시적 기본 생성자 본체는 비어있는 함수입니다.

사용자가 하나라도 생성자를 만들게 되면 암시적 기본 생성자는 생성되지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

class MyClass
{
public:
    int mValue{1};

    MyClass(int value)
    {
      mValue = value;
    }
};

int main()
{
    // 매개변수 하나를 받는 생성자를 만들었기 때문에 
    // 암시적 기본 생성자는 생성되지 않음
    // 따라서 MyClass는 기본 생성자가 없음
    MyClass c1; // 에러

    MyClass c2{1}; // 매개변수가 1개인 생성자로 인스턴스화
}

클래스에 매개변수가 있는 생성자가 존재하는 상태에서 기본 생성자를 사용하고 싶은 경우에 C++11 부터 default 키워드를 사용하여 암시적 기본 생성자를 명시할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
class MyClass
{
public:
    int mValue{1};

    MyClass() = default;
    MyClass(int value)
    {
      mValue = value;
    }
};



📄객체 초기화 방법

멤버변수 초기화는 복사 대입이 아니라 멤버 초기화 리스트를 사용하도록 합니다.

  • 멤버 초기화 리스트
1
2
3
4
5
6
7
8
9
10
11
12
class Quest
{
private:
    int mID;
    int mExp;

public:
    Quest(int id = 1, int exp = 1) : mID(id), mExp(exp)
    {

    }
}


멤버 초기화 리스트는 유니폼 초기화도 가능합니다.

1
2
3
4
Quest(int id = 1, int exp = 1) : mID{id}, mExp{exp}
{

}


멤버 초기화 리스트는 기본 생성자에서도 가능하고 클래스 내부에 상수인 멤버 변수가 있어도 사용가능합니다.

1
2
3
4
Quest() : mID{ 1 }, mExp{ 1 }
{

}


1
2
3
4
5
6
7
8
9
10
11
12
13
class Quest
{
private:
    int mId;
    int mExp;
    const int mNPC;

public:
    Quest() : mId{ 1 }, mExp{ 1 }, mNPC{ 1 }
    {

    }
};



📄대리 생성자

C++에서 생성자를 함수처럼 사용할 수 없기 때문에 대리 생성자 기능이 추가되었습니다. 대리 생성자는 멤버 초기화 리스트 처럼 콜론 기호 뒤에 사용합니다. 동일한 기능을 하는 생성자는 중복 코드를 방지하기 위해 대리 생성자를 사용해야 합니다.

1
2
3
4
5
6
7
8
9
Quest() : mID{ 1 }, mExp{ 1 }
{

}

Quest(std::string name) : Quest{}
{

}




📚소멸자

소멸자는 생성자와 같은 특별한 멤버함수입니다. 객체의 소멸에 사용되는 기능을 가지고 있는데 객체를 없애는 것이 아니라 해당 메모리 공간을 깨끗하게 비우는 기능을 합니다.

멤버가 동적으로 할당된 상태라면 소멸자가 매우 중요합니다.

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
#include <iostream>

class MyArray
{
private:
    int mLength;
    int* mArray;

public:
    MyArray(int length) : mLength{ length }
    {
        mArray = new int[length] {};
    }

    ~MyArray()
    {
        delete[] mArray;
    }
};

int main()
{
    MyArray array{10};
    // 소멸자가 없다면 int 10개 크기의 메모리는 할당된 상태로 유지
    // 메모리 누수 발생
    // 따라서 생성자를 만들었다면 항상 소멸자를 작성!
}



Leave a comment