Computer >> 컴퓨터 >  >> 프로그래밍 >> C++

C++ 생성자 내부에서 가상 함수를 호출하면 어떻게 될까?

생성자에서 가상 함수 호출의 문제점

생성자나 소멸자 내부에서 가상 함수를 호출하는 것은 매우 위험하며, 가급적 피하는 것이 좋습니다. 그 이유는 호출된 가상 함수가 파생 클래스(Derived)의 버전이 아니라 기반 클래스(Base)의 버전으로 실행되기 때문입니다.

C++에서는 모든 클래스가 자신의 생성 과정에 진입하기 전에 먼저 자신만의 가상 메서드 테이블(vtable)을 구성합니다. 따라서 생성자 안에서 가상 메서드를 호출하면 기반 클래스의 가상 메서드가 실행됩니다. 만약 해당 계층에 구현이 전혀 존재하지 않는다면 순수 가상 함수 호출(pure virtual method call) 오류가 발생하여 프로그램이 비정상 종료될 수 있습니다.

기반 클래스의 생성이 완전히 끝나면, 그때서야 컴파일러는 파생 클래스를 구성하기 시작하고 메서드 포인터들을 파생 클래스의 구현을 가리키도록 재설정합니다. 즉, 생성자가 실행되는 시점에는 아직 객체가 완전하지 않은 상태이므로 다형성(polymorphism)이 의도한 대로 동작하지 않는 것입니다.

예를 들어 다음과 같은 코드가 있다고 가정해 보겠습니다.

예제 코드

#include<iostream>
using namespace std;

class Base {
public:
    Base() { f(); }
    virtual void f() { std::cout << "Base" << std::endl; }
};
class Derived : public Base {
public:
    Derived() : Base() {}
    virtual void f() { std::cout << "Derived" << std::endl; }
};

int main() {
    Derived d;   
    return 0;
}

실행 결과

위 코드를 컴파일하고 실행하면 다음과 같은 결과가 출력됩니다.

Base

결과 분석

Derived 객체 d를 생성하는 순간, 컴파일러는 먼저 Base 클래스의 생성자를 호출합니다. 이 시점에서는 아직 Derived 부분이 초기화되지 않았기 때문에, Base 생성자 내부의 f() 호출은 Derived::f()가 아니라 Base::f()를 실행하게 됩니다.

따라서 많은 개발자가 기대하는 Derived가 아니라 Base가 출력됩니다. 이는 버그가 아니라 C++ 표준에 명시된 정상적인 동작이며, 객체는 기반 클래스부터 순차적으로 구성되기 때문입니다.

정리 및 주의사항

생성자와 소멸자 내부에서 가상 함수 호출은 설계 단계에서 반드시 고려해야 할 중요한 함정입니다. 초기화 로직에서 파생 클래스의 재정의된 동작이 필요하다면, 가상 함수 대신 팩토리 함수 패턴이나 별도의 초기화 메서드(init())를 활용하는 것이 안전한 대안이 될 수 있습니다.