C++이나 C#의 생성자나 소멸자에서 가상 함수를 호출하지 말 것

C++이나 C#의 생성자나 소멸자에서는 그 클래스의 가상 함수를 호출하면 안 됩니다. C++에서는 소멸자에서 파생 클래스의 가상 함수가 호출되지 않고, 기반 클래스의 가상 함수가 호출돼 버리기 때문입니다. C#은 다른 문제가 있는데, <More Effective C#: 50 Specific Ways to Improve Your C#>의 ‘Item 48: Avoid Calling Virtual Functions in Constructors’를 보시면 됩니다.

C++에서 상황은 다음과 같습니다.

class base_type
{
    base_type() { do(); }
protected:
    virtual void do() {}
};

class derived_type : public base_type
{
protected:
    virtual void do() {}
};

derived_type derived;

위와 같은 때, derived가 생성될 때 호출되는 do 함수는 derived_type의 것이 아니라 base_type의 것입니다.

<C++ FAQs Second Edition> 책의 283쪽과 284쪽을 보면, 이 문제에 대해 간단히 다루고 있습니다. Never Call Virtual Functions during Construction or Destruction에서도 이 문제를 설명하고 있네요. 이 문제는 C++ 컴파일러가 경고해 줘야 하는 게 아닌가 생각이 들 정도로, 간과하기 쉽지만 찾아내기 어려운 버그를 만들 수 있습니다. 파생 클래스의 가상 함수가 연결된 상태에서 기반 클래스의 생성자나 소멸자가 호출된다면 이 문제를 원천적으로 없앨 수 있는데, 이렇게 하지 않은 건 C++ 언어를 만든 사람이 명백히 실수한 부분입니다.

생성자는 기반 클래스의 것이 먼저 호출되고, 그 후에 파생 클래스의 것이 호출됩니다. 즉, 기반 클래스의 생성자가 호출되는 시점엔 파생 클래스의 객체가 아직 생성되지 않은 것입니다. C++에서는 파생 클래스의 객체가 생성되지 않았다면, 파생 클래스의 재정의한 가상 함수도 호출할 수 없다고 합니다. 그래서 C++은 이미 아는 정보만 갖고 가상 함수를 호출하게 되고, 그 결과로 기반 클래스의 가상 함수가 호출됩니다.

소멸자에서도 마찬가지입니다. 기반 클래스의 소멸자가 불리는 시점엔 파생 클래스가 이미 소멸한 상태입니다. 따라서 가상 함수를 호출하면, 파생 클래스의 것이 호출되는 게 아니라 기반 클래스의 것이 호출돼 버립니다.

이 문제를 다르게 표현하면, 생성자나 소멸자에서는 그 클래스의 가상 함수가 가상적으로 동작하지 않는다고 보면 됩니다.

이 문제를 해결하는 방법으로 다음 네 가지가 생각납니다.

  1. 생성자나 소멸자에서 불리는 함수를 모두 비가상 함수로 만드는 방법
  2. 생성자나 소멸자에서 함수 호출을 하지 말고 생성과 소멸에 관계된 별도의 가상 함수를 만들어 생성 직후나 소멸 직전에 불러 주는 방법
  3. 기반 클래스의 것이 비가상적으로 호출된다는 것을 base_type::function()처럼 명시적으로 표기하는 방법
  4. 무시하는 방법

그런데 아쉽게도 다음과 같은 이유로, 위의 네 가지 방법 모두 그다지 좋다고 할 수 없습니다.

첫 번째 방법: 클래스의 동작이 유연해지지 않습니다. 게다가, 생성자에서 다른 객체의 함수를 부르는 때엔 호출당한 객체의 함수가 호출한 객체의 가상 함수를 호출하는 것을 찾아 내 고치기가 어렵습니다.

두 번째 방법: 별도의 생성/소멸 함수 호출을 빠트리는 때가 생길 수 있어서 버그가 생길 여지가 많아집니다. 그리고 코딩이 번거로워지는데, 특히 스마트 포인터나 가비지 컬렉션을 사용할 때엔 소멸 시점 처리가 어렵기 때문에 더욱 그렇습니다.

세 번째 방법: 다른 사람이 코드의 의도를 모르고 비가상 호출 표기를 없애 버릴 수 있습니다. 그리고 생성자에서 다른 객체의 함수를 부르는 때엔 호출당한 객체의 함수가 호출한 객체의 가상 함수를 호출하는 것을 찾아서 고치기가 어렵습니다.

네 번째 방법: 중대한 버그를 놓치고 지나갈 우려가 있습니다.

기반 클래스의 생성자나 소멸자에서 그 클래스의 가상 함수를 호출하는 것은 위험합니다. 그렇게 코드를 만들면 의도한 대로 동작하지 않을 수 있으며, 그 버그의 이유를 찾아내기가 어렵습니다. 그런데 더 큰 문제는, 이 문제에 대한 좋은 해결책이 없다는 것입니다.

Advertisements

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중