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

C++에 가상 생성자(Virtual Constructor)가 없는 이유

가상 메커니즘의 동작 조건

C++의 가상(virtual) 메커니즘은 기본 클래스(base class) 포인터가 파생 클래스(derived class) 객체를 가리킬 때에만 작동합니다. 즉, 런타임에 실제 객체의 타입을 판단하여 올바른 함수를 호출하는 방식이므로, 이미 생성된 객체와 그 객체 내부의 가상 테이블(vtable) 정보가 필요합니다.

생성자는 왜 가상으로 만들 수 없는가?

C++에서 생성자는 가상 함수로 선언할 수 없습니다. 그 이유는 다음과 같습니다.

  • 클래스의 생성자가 실행되는 시점에는 아직 메모리에 가상 테이블(virtual table)이 만들어져 있지 않습니다.
  • 가상 테이블이 없다는 것은 가상 포인터(vptr)도 아직 설정되지 않았다는 의미입니다.
  • 가상 함수 호출은 vptr을 통해 vtable을 참조하여 이루어지기 때문에, vtable이 존재하기 전인 생성 과정에서는 가상 메커니즘이 동작할 수 없습니다.

또한 생성자의 역할 자체가 객체의 타입을 확정하고 초기화하는 것이므로, 호출 시점에 어떤 생성자를 실행할지 반드시 정적으로 결정되어야 합니다. 따라서 생성자는 항상 비가상(non-virtual)이어야 합니다.

대신 가상 소멸자(Virtual Destructor)는 가능하다

반면 소멸자는 가상으로 선언하는 것이 가능하고, 오히려 권장됩니다. 기본 클래스 포인터로 파생 클래스 객체를 delete할 때 소멸자가 가상이 아니라면 기본 클래스의 소멸자만 호출되어 파생 클래스의 자원이 해제되지 않는 문제가 발생할 수 있기 때문입니다. 다음 예제를 통해 확인해 보겠습니다.

예제 코드

#include<iostream>
using namespace std;
class b {
   public:
   b()
   { cout<<"Constructing base \n"; }
   virtual ~b()
   { cout<<"Destructing base \n"; }
};
class d: public b {
   public:
      d()
      { cout<<"Constructing derived \n"; }
      ~d()
      { cout<<"Destructing derived \n"; }
};
int main(void) {
   d *derived = new d();
   b *bptr = derived;
   delete bptr;
   return 0;
}

실행 결과

Constructing base
Constructing derived
Destructing derived
Destructing base

결과 분석

객체가 생성될 때는 기본 클래스 생성자가 먼저 호출된 후 파생 클래스 생성자가 호출됩니다. 반면 소멸 순서는 그 반대로, bptr이 기본 클래스 타입의 포인터임에도 불구하고 소멸자가 virtual로 선언되어 있기 때문에 파생 클래스의 소멸자가 먼저 호출되고, 이후 기본 클래스의 소멸자가 호출됩니다.

만약 소멸자가 가상이 아니었다면 delete bptr; 실행 시 기본 클래스의 소멸자만 호출되어 파생 클래스의 리소스가 제대로 해제되지 않는 미정의 동작(undefined behavior)이나 메모리 누수가 발생할 수 있습니다. 이처럼 상속 구조에서 다형성을 활용하는 클래스는 반드시 가상 소멸자를 선언해야 안전합니다.