Corgi Dog Bark

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [c++] 너 .. 혹시 생성자에서 가상함수 호출할거야 ?
    뜯고 또 뜯어보는 컴퓨터/씨쁠쁠 C++ 2022. 11. 9. 00:54
    반응형
    네.. 제목을 좀 재밌게 지어보려고 했는데, 의도된 대로 지어졌는지 모르겠습니다. ㅎㅎ 아무튼 오늘 글을 쓰려고 한 주제는 생성자에서 가상 함수 호출을 하면 어떻게 되는가? 가 주제였습니다. 결론부터 말하자면,
    "제발 하지 마"입니다..!

     

    뭐가 문제일까?


    가상 함수를 생성자에서 호출하기 전에, "뭐가 문제일까?" 하는 생각이 드실 수 도 있습니다. 다음 함수를 보고 결과를 맞추신다면 이 글을 읽으실 필요는 없으실 거 같습니다. 그럼 문제입니다. 다음 코드를 보시고 어떤 결과가 나올지 예측해 주세요!

    #include <iostream>
    
    class A
    {
    public:
      A() { fn(); }
    
      virtual void fn() { _n = 1; }
      int getn() { return _n; }
    
    protected:
      int _n;
    };
    
    class B : public A
    {
    public:
      B() : A() {}
    
      virtual void fn() { _n = 2; }
    };
    
    int main()
    {
      B b;
      int n = b.getn();
      std::cout << n << std::endl;
    }

    정답 확인

    더보기

    답: n = 1 이 되어, 1이 호출됩니다.

    왜 이런 현상이 일어날까요? 처음 제 생각은 당연히 B( ) 가 호출되고, B 의 생성자에서 가상 함수 호출하게 된다면 B의 fn( ) 즉 오버 라이딩된 B.fn( ) 이 호출될 줄 알았습니다.

     

    근데 n = 1 즉, A.fn( ) 이 호출된 것 같습니다. 그 이유는 무엇일까.. 고민을 해보고 조사하여 이 글을 작성하게 되었습니다.

     

    반응형

    virtual function( )의 호출


    가상 함수가 호출될 때에는 현재의 생성자 계층구조에 맞게 호출이 되어집니다. 따라서 생성자에 한해서, 가상함수 메커니즘은 불가능하게 되는데, 이는 파생된(derived) 클래스가 아직 생성되지 않았기 때문입니다. 쉽게 말하자면, 파생된 클래스가 생성되기 전에, 부모 클래스가 생성이 되고, 파생된 클래스는 vfPtr에 반영이 되지 않았습니다. 따라서 부모 클래스의 함수를 호출하게 됩니다.

     

    정리해보겠습니다.

    1. 처음에 부모 클래스의 생성자를 호출하게 되고, 부모 클래스가 생성되고 나면,
    2. 자식 클래스 객체를 생성하게 됩니다. 이렇게 된다면, 이제야 오버 라이딩된 함수 객체를 등록하게 되어, 다형성(Polymorphysm)을 가질 수 있게 됩니다.
    3. 자식 클래스가 생성되기전, 부모 클래스가 호출되게 되면 부모 클래스의 가상 함수가 호출되게 됩니다.

     

    생성자는 부모로부터 자식 클래스로 불려지기 때문에 vTable에 반영이 안 된 자식 클래스는 생성자 호출에서 위험할 수 있습니다.(의도된 대로 동작을 안할 수 있습니다.) 따라서, 가상 함수를 생성자에서 호출하는 일은 절대 절대 없도록 주의해야겠습니다.! 특히나 이 경우, 컴파일러가 에러나 다른 메시지를 출력하지 않으므로 더욱 조심하셔야 합니다.!

     

    QUIZ : 다음 코드의 출력?


    #include <iostream>
    
    class A {
    public:
      A() { std::cout << "A()\n"; };
      virtual void foo() { std::cout << "A::foo()\n"; };
      virtual void bar() { std::cout << "A::bar()\n"; };
    };
    
    class B : public A {
    public:
      B() : A() {
        std::cout << "B()\n";
        foo();
        bar();
      };
      void bar() { std::cout << "B::bar()\n"; };
    };
    
    class C : public B {
    public:
      C() : B() { std::cout << "C()\n"; };
      void foo() { std::cout << "C::foo()\n"; };
      void bar() { std::cout << "C::bar()\n"; };
    };
    
    int main() {
      C x;
      return 0;
    }

    정답확인

    더보기

    A() // 생성자 A( ) 가 호출되어 지고,
    B() // B 가 생성되면서 호출 되어집니다.
    A::foo() // 여기서 foo( ) 함수는 B에서 오버라이딩이 되지 않았으므로, A에서 호출되어지고,
    B::bar() // B가 호출한 bar( ) 함수는 B 에서 오버라이딩이 되었으므로, 호출되어 집니다.
    C() // C 에서는 B( )의 생성자가 호출했었을시 C 가 생성되지 않았으므로, C( ) 가 호출되어집니다.

    반응형

    댓글

Designed by Tistory.