프로그래밍/C++

C++] 가상 함수를 생성자에 사용하면??

Hwan2 2020. 6. 22. 01:26
반응형

최근 기업 시험을 보는데 이런 문제가 나왔습니다.

"가상함수를 생성자에 사용한다면 어떻게 되는가?"


이 문제를 봣을 때 "이건 뭐지??" 라는 생각이 먼저 들었습니다.


왜냐하면 생성자에서 가상함수를 호출할 생각 자체를 제 평생 한번도 해본적이 없기 때문입니다.

왜냐??

사용할 이유가 없으니깐요.... 가상 함수니깐요....

가상 함수의 의미는? overriding 이죠. 다형성이죠. 

또한 순수 가상 함수는 재 정의를 해줘야하는 함수입니다.

재 정의를 해야할 함수를 생성자를 통해 호출을한다??


아마 사용자의 실수로 호출을 하지 않을까... 싶네요....

코드가 길어지면 어느 클래스에 어떤 맴버함수가 있는지 잘 기억이 안나니깐 말이죠...


그래서 저는.... 찾아보고....경험해보고.... 그래서 이 글을 쓰게 됩니다.....


그럼 코드를 한번 보시죠!!


예제 1

#include <iostream>

class A
{
public:
    A() { fn(); }

    virtual void fn() { _n = 1; }
    int getn() { return _n; }

protected:
    int _n;
};

class B : public A
{
public:
    virtual void fn() { _n = 2; }
};

int main(void) {
    B b;
    printf("%d\n"b.getn());
    b.fn();
    printf("%d\n"b.getn());
    return 0;
}


자!! 결과가 이상합니다. 생각했던 시나리오를 읊어보겠습니다.

1. B b; 객체를 만든다.

2. 상속관계 이므로 부모 생성자가 먼저 호출된다.

3. 부모 생성자 안에 fn(); 함수를 호출해주네??

4. fn() 함수는 가상함수네??

5. 근데 지금 호출한 객체는 class B 의 객체네??

6. 그럼 v-table에 의거해 B의 함수가 호출 되겠지? ㅎㅎ


이런 시나리오 입니다.

하지만 정~말 잘못된 생각이구요.

그럼 코드의 흐름대로 읊어보겠습니다.

1. B b; 객체를 만든다.

2. 상속관계 이므로 부모 생성자가 먼저 호출된다.

3. 부모 생성자 안에 fn(); 함수를 호출해주네??

4. 응~ 부모 함수 호출해!!

5. ???


왜 v-table에 의거하여 override된 함수가 호출이 안될까요??


A생성자가 호출되는 지점에서 디스어셈블리를 한 모습입니다.

여길 보시면 A의 v-table이 호출되는걸 알 수 있습니다.

B의 v-table이 아니란 말이죠.

이 이유는 초기화에 있습니다.


생성자의 특징은 딱 1번 호출만 된다는 점입니다.

그리고 이 호출 과정에선 자기 자신의 객체만 볼 수 있다는 점입니다.

다시말해서 virtual로 함수가 override됬어도, 해당 생성자 입장에선 자기자신 클래스만 본다는 점입니다.


왜그럴까요??

메모리에 적제될 때 부모 맴버변수들 부터 적재됩니다. 다시말해서 A 클래스의 생성자가 호출되는 시점에선

메모리상엔 자식 클래스에 대한 데이터 값은 없는 것이죠. 또한 앞서 말했듯 생성자 입장에선 자기 자신밖에 볼 수 없습니다.

초기화 과정이니깐요.


다른 예제를 봐볼까요??


예제 2

#include <iostream>

class A
{
public:
    A() { fn(); }

    virtual void fn() { _n = 1; }
    int getn() { return _n; }

protected:
    int _n;
};

class B : public A
{
public:
    virtual void fn() { _n = 2; }
};

int main(void) {
    A* b = new B();
    printf("%d\n"b->getn());

    return 0;
}


포인터로 바꾸고, upcast를 해도 똑같죠??

new B()가 먼저 실행되면서 예제 1번과 똑같이 흘러가기 때문입니다.



더 최악인 예제를 봅시다.


예제3

#include <iostream>

class A
{
public:
    A() { fn(); }

    virtual void fn() = 0;
    int getn() { return _n; }

protected:
    int _n;
};

class B : public A
{
public:
    virtual void fn() { _n = 2; }
};

int main(void) {
    A* b = new B();
    printf("%d\n"b->getn());

    return 0;
}


순수 가상 함수를 부모 클래스에서 호출하게된다면???

이건 실행조차 안됩니다.


왜 실행조차 안될까요??

바로 정의도 되지 않은 fn(); 함수를 호출하기 때문입니다.


왜냐?? 예제 1번과 마찬가지로 A생성자가 호출되는 시점에선 B 클래스를 볼 수 없기 때문에,

정의되지 않은 순수 가상 함수를 호출하게 되는것이죠.

그럼 링크에서 error가 날 수밖에 없는 것이죠.....


정의도 안됬는데..... 저기에 어떤 값을 넣을 수도 없는데.... 컴파일된 오브젝트 파일을 어떻게 링크 하냐!!....



쩝... 이상입니다.


반응형