Virtual functions in ctor and dtor

During construction and destruction of an object you are able to call virtual functions. But you should not do this. That’s because it is a source for errors as the behavior of the application may be differ from your expectations or the expectations of other developers which have to change your code in future.

Before we start to think about the ctor and dtor issue we want to have a look at the normal behavior of a virtual function. The following source code shows a base class with a virtual function which creates some logging output and another function which executes something and writes a log entry. A derived class will use the base class implementations but overwrites the virtual function for the log entry. So we can create an object of the derived class, executed the functions provided by the base class and the log information of the derived class is used as we have overwritten the virtual function.

class BaseClass
{
public:
  void DoSomething()
  {
    //do something...

    //... and log what you habe done
    LogClassInformation();
  }

  virtual void LogClassInformation()
  {
    std::cout << "BaseClass log" << std::endl;
  }
};

class DerivedClass : public BaseClass
{
public:
  void LogClassInformation()
  {
    std::cout << "DerivedClass log" << std::endl;
  }
};


int _tmain(int argc, _TCHAR* argv[])
{
  DerivedClass myClass = DerivedClass();

  myClass.DoSomething();

  return 0;
}

 

But why is it dangerous to make a virtual function call in ctor and dtor? Let us think about the execution order of ctor and dtor to understand the issue. Again we have a base class and a derived class. If we create an object of the derived class, the ctor of the base class is called first followed by the ctor of the derived class. If you base class ctor will call a virtual function which is overwritten by the derived class we now have the issue that we want to execute some functionality of a class which is not initialized by a ctor yet. So the class members may contain not initialized members and the function execution can result in an undefined behavior. As this is dangerous, C++ gives you no way to call the derived class function. That is such a fundamental language behavior that during base class construction the type of the object is that of the base class itself and not the derived class.

The same is true for the dtor. During destruction of the class the dtor of the derived class was already called when the dtor of the base class is executed. It is not safe to call a function of the derived class as the class members are no longer valid. Therefore C++ changes the object type during the dtor call to the base class object on entry of the base class dtor.

The following source code shows an according example.

class BaseClass
{
public:
  BaseClass()
  {
    std::cout << "BaseClass ctor" << std::endl;
    LogClassInformation();
  }

  ~BaseClass()
  {
    std::cout << "BaseClass dtor" << std::endl;
    LogClassInformation();
  }

  virtual void LogClassInformation()
  {
    std::cout << "BaseClass log" << std::endl;
  }
};

class DerivedClass : public BaseClass
{
public:
  DerivedClass()
  {
    std::cout << "DerivedClass ctor" << std::endl;
    LogClassInformation();
  }

  ~DerivedClass()
  {    
    std::cout << "DerivedClass dtor" << std::endl;    
    LogClassInformation();
  }

  void LogClassInformation()
  {
    std::cout << "DerivedClass log" << std::endl;
  }
};


int _tmain(int argc, _TCHAR* argv[])
{
  DerivedClass myClass = DerivedClass();
  
  return 0;
}

The console application will create the following output.

BaseClass ctor

BaseClass log

DerivedClass ctor

DerivedClass log

DerivedClass dtor

DerivedClass log

BaseClass dtor

BaseClass log

 

As you can see the base class ctor and dtor will call the base class logging function and not the one of the derived class. After reading this article you may argue that this is the expected behavior and therefore it is safe to implement in that way. But it is recommended to avoid such implementations as it is a source for errors. Within the short example it is easy to understand the source code but in real applications such implementations are a good source for errors.

Furthermore within this example the virtual function is implemented within the base class and the derived class. So we have a valid and running application. But often you explicitly want to move the responsibility for implementation to the derived class. In this case you can create a pure virtual function. The following source code shows the adapted example.

class BaseClass
{
public:
  BaseClass()
  {
    std::cout << "BaseClass ctor" << std::endl;
    LogClassInformation();
  }

  ~BaseClass()
  {
    std::cout << "BaseClass dtor" << std::endl;
    LogClassInformation();
  }

  virtual void LogClassInformation() = 0;
};

class DerivedClass : public BaseClass
{
public:
  DerivedClass()
  {
    std::cout << "DerivedClass ctor" << std::endl;
    LogClassInformation();
  }

  ~DerivedClass()
  {    
    std::cout << "DerivedClass dtor" << std::endl;    
    LogClassInformation();
  }

  void LogClassInformation()
  {
    std::cout << "DerivedClass log" << std::endl;
  }
};


int _tmain(int argc, _TCHAR* argv[])
{
  DerivedClass myClass = DerivedClass();
  
  return 0;
}

Now thinks will become more difficult. What’s the expected behavior now? Of course the dtor and ctor execution order stays the same and therefore the base class logging function is called. But this time it is a pure virtual function and cannot be called. Some compilers will recognize such errors and report a linking issue other compilers will not recognize the issue which will result in an error during runtime.

Summary

Do not call virtual functions during construction or destruction, because such calls will never go to a more derived class than that of the currently executing constructor or destructor. This may result in undefined behavior and is a source of errors.

Werbung
Dieser Beitrag wurde unter C++ veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s