Game Develop

객체 생성 및 소멸과정중에는 절대로 가상함수를 호출하면 안된다. 본문

C++/Effective C++

객체 생성 및 소멸과정중에는 절대로 가상함수를 호출하면 안된다.

MaxLevel 2025. 11. 12. 22:17

사실 Effective C++이라는 카테고리를 따로 걸어서 뭔가 올리고 있긴 한데, 다른 내 글들과 마찬가지로 사실상 숙지용? 글에 가깝다.

책처럼 보기 좋게 쓰여진다기 보다는, 공부하다 혼잣말 하는 느낌의 글이라 생각한다.

어차피 Effective 시리즈 관련 정리 글은 다른 분들이 잘 정리해서 올려주신게 많기 때문에...

 

=== === === === === === === === === === === === === === === === === === === === === === === === === === 

 

 

부모와 자식클래스가 있고, 부모클래스에서 Test() 라는 가상함수를 선언했다고 가정해보자.

그리고 자식에서는 그 가상함수를 오버라이드해서 뭐 이것저것 작성했고.

그다음 이 가상함수를 부모의 생성자에서 호출하려 하면? 안된다. 

아마 개발자가 의도하지 않은 상황이 나올 가능성이 높다.

 

자 아래와 같이 아주 간단한 코드를 실행해보자.

 

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
#include <iostream>
 
using namespace std;
 
class Parent
{
public:
    Parent()
    {
        TestVirtualFunc();
    };
 
    virtual void TestVirtualFunc()
    {
        cout << "Parent TestVirtualFunc " << endl;
    }
 
    ~Parent() {};
};
 
class Child : public Parent
{
public:
    Child() {};
 
    virtual void TestVirtualFunc() override
    {
        
        cout << "Child TestVirtualFunc " << endl;
    }
 
    ~Child() {};
};
 
 
int main()
{
    Child* child = new Child();
}
cs

 

 

보면 부모의 생성자에서 가상함수 TestVirtualFunc()을 호출했는데, 아마 개발자의 의도는 결국 오버라이드된 자식클래스의 가상함수일 것이다.

 

하지만 결과는?

 

 

 

애석하게도 부모께 호출된다.

 

왜냐하면 아직 부모객체가 정상적으로 생성자 호출이 완료되지 않았기 때문이다.

생성자호출이 끝나기 전에 오버라이드된 자식의 가상함수를 호출해버렸지 않았는가?

감이 좋은 사람들은 이미 이상함을 눈치 챘을수도 있다. 부모의 객체가 초기화되지도않았는데 자식의 함수를 호출하다니?

 

본디 상속관계의 객체를 만들려고 할 경우, 부모의 생성자 호출 이후에 자식의 생성자가 호출된다.

부모객체가 정상적으로 초기화가 이루어졌다는 것을 기준으로, 자식객체의 초기화를 진행하는것이 정상적인 흐름이라는 것이다.

그렇기 때문에 아예 생성자에서 가상함수를 호출할 경우, 자식쪽으로 안내려가게 언어레벨에서 막아놨다는 것.

 

좀 더 확실한 이야기를 하자면, 애초에 부모클래스가 생성되는 동안은, 그 객체의 타입은 부모클래스로 고정시켜 놨다는 것이다.

그렇기 때문에 생성자에서 가상함수를 호출해도 자식으로 못내려가는것이다.

 

이 때 런타임 타입정보 (RTTI)를 사용하는 행위 (dynamic_cast라던가 typeid)를 사용하더라도 부모객체로 취급받는다는 것이다.

그러니 자식의 가상함수를 호출할 리가 없다는 것.

 

비슷한 느낌으로 소멸자를 처리하는 과정을 봐보자.

부모클래스의 소멸자를 가상소멸자로 해놨다면, 정상적으로 자식소멸자를 먼저 호출한다음 부모껄 호출할 것이다.

이 때, 자식소멸자를 처리하는 과정에서는 객체타입이 자식클래스인데, 자식소멸자 처리 이후 부모소멸자로 넘어가면 객체타입이 부모타입으로 바뀐다는 것이다.

 

 

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
#include <iostream>
 
using namespace std;
 
class Parent
{
public:
    Parent()
    {
        //TestVirtualFunc();
    };
 
    virtual void TestVirtualFunc()
    {
        cout << "Parent TestVirtualFunc " << endl;
    }
 
    ~Parent()
    {
        cout << typeid(*this).name() << endl;
    };
};
 
class Child : public Parent
{
public:
    Child() {};
 
    virtual void TestVirtualFunc() override
    {
        
        cout << "Child TestVirtualFunc " << endl;
    }
 
    ~Child()
    {
        cout << typeid(*this).name() << endl;
    };
};
 
 
int main()
{
    Child* child = new Child();
 
    delete child;
}
cs

 

 

 

 

보다시피 자식소멸자 호출할 땐 typeid에 Child가 찍히고, 부모소멸자 호출할 땐 Parent가 찍히는 걸 확인 할 수 있다.