Base class constructors cannot call virtual functions

When a base class constructor is executing, the object is a base class object. Any virtual function calls execute the base class virtual functions, not the overriding functions in the (as yet unconstructed) derived class. But sometimes some initialization associated with the base class must be done using derived class (overriding) virtual functions. To handle such a case, it is necessary to call a special virtual function immediately after constructing the instance. Always name this type of function Initialize.

NOTE Derived classes only override Initialize() if they want to add their own post-construction initialization calls.

For example, sometimes during construction you need to create items that can't be created until the most derived class is constructed. Suppose you have a guard element of a linked list, where overridding DoMakeLink() in a derived class can change the type of the link element. By postponing the Initialize() call to the most derived constructor, you call the proper DoMakeLink.

Never require the client to call a separate virtual Initialize() function to finish initialization after constructing all bases. Requiring the client to remember this is extremely error prone; if the client forgets, the object will not be properly constructed.

The cost of failing to call Initialize()

The overhead of checking for failure to call Initialize() is comparable to or greater than the following alternative schemes for achieving the same end:

Lazy evaluation. In every client function, check a flag (set to FALSE in the constructor); then complete initialization by calling the virtual initializer at that time. This works well only in a limited number of cases.

External/internal constructors. Classes in the hierarchy affected by the need for virtual initialization should have both internal (protected) and external (public) constructors. The internal constructors don't call virtual Initialize(). The external constructors call the internal one (actually, a shared private function), as well as the internal constructors of all bases. The external constructors then call the virtual Initialize(). This is error prone too, but only for subclasses as opposed to all clients.

Clients call the external constructor, and derived classes call the internal constructors of their base classes. This method calls the virtual Initialize() for the most derived class only, the one the client constructed directly.

      class Base {
      public:
          Base() {InitBase(); Initialize();};
      protected:
          virtual void Initialize();
          enum {kInternal} InternalMarker;
          Base(InternalMarker) {InitBase();};
      private:
          void InitBase();
      };
      
      class Derived: public Base {
      public:
          Derived() : Base(kInternal) {InitDerived(); Initialize(););
      protected:
          virtual void Initialize();
          Derived(InternalMarker) {InitDerived();};
      private:
          void InitDerived();
      };
Use virtual base class semantics. Include a virtual base class in the affected class hierarchy, and make use of the fact that only the outermost call to that base class' constructor is used. The Taligent Application Environment will include a class similar to this:

      #include <typeinfo.h>
      #include <stdio.h>
      class VInitialize {
      public:
          VInitialize(const typeinfo& t) : fCompleteType(t) {}
          void CheckForInitialize() { if (typeid(*this) == fCompleteType) Initialize(); }
          void CheckForFinalize() { if (typeid(*this) == fCompleteType) Finalize(); }
          virtual void Initialize() = 0;
          virtual void Finalize() = 0;
      private:
          const typeinfo& fCompleteType;
      };
      
      
    
      class Base {
      public:
          Base() {}
      };
      
      
    
      class JoesParent : public Base, public virtual VInitialize {
      public:
          JoesParent() : Base(), VInitialize(typeid(JoesParent)) {
              CheckForInitialize();       // normal initialization
          };
          ~JoesParent() {
              CheckForFinalize();         // normal finalization
          }
      protected:
          virtual void Initialize();      // only called if this is the complete class
          virtual void Finalize();        // only called if this is the complete class
      };
      
      class JoeClass: public JoesParent {
      public:
          JoeClass() : JoesParent(), VInitialize(typeid(JoeClass)) {
              CheckForInitialize();       // normal initialization
          }
          ~JoeClass() {
              CheckForFinalize();         // normal finalization
          }
      protected:
          virtual void Initialize();      // only called if this is the complete class
          virtual void Finalize();        // only called if this is the complete class
      };
      
      void JoesParent::Initialize() { puts("Called JoesParent::Initialize()"); }
      void JoesParent::Finalize()   { puts("Called JoesParent::Finalize()"); }
      void JoeClass::Initialize()   { puts("Called JoeClass::Initialize()"); }
      void JoeClass::Finalize()     { puts("Called JoeClass::Finalize()"); }
      
      int main() {
          puts("--- creating a JoesParent ---");
          JoesParent *x = new JoesParent;
          puts("--- creating a JoeClass ---");
          JoeClass *y = new JoeClass;
          puts("--- deleting a JoesParent ---");
          delete x;
          puts("--- deleting a JoeClass ---");
          delete y;
          return 0;
      }
      
      
    
      --- creating a JoesParent ---
      Called JoesParent::Initialize()
      --- creating a JoeClass ---
      Called JoeClass::Initialize()
      --- deleting a JoesParent ---
      Called JoesParent::Finalize()
      --- deleting a JoeClass ---
      Called JoeClass::Finalize()

[Contents] [Previous] [Next]
Click the icon to mail questions or corrections about this material to Taligent personnel.
Copyright©1995 Taligent,Inc. All rights reserved.

Generated with WebMaker