views:

853

answers:

7

Suppose I have two C++ classes:

class A
{
public:
  A() { fn(); }

  virtual void fn() { _n = 1; }
  int getn() { return _n; }

protected:
  int _n;
};

class B : public A
{
public:
  B() : A() {}

  virtual void fn() { _n = 2; }
};

If I write the following code:

main()
{
  B b;
  int n = b.getn();
}

One might expect that n is set to 2.

It turns out that n is set to 1. Why?

+1  A: 

The reason is that C++ objects are constructed like onions, from the inside out. Super-classes are constructed before derived classes. So, before a B can be made, an A must be made. When A's constructor is called, it's not a B yet, so the virtual function table still has the entry for A's copy of fn().

David Coufal
C++ does not normally use the term "super class" - it prefers "base class".
anon
That is the same in most OO languages: you cannot possibly build a derived object without the base part being already constructed.
David Rodríguez - dribeas
+21  A: 

Calling virtual functions from a constructor or destructor is dangerous and should be avoided whenever possible. All C++ implementations should call the version of the function defined at the level of the hierarchy in the current constructor and no further.

The C++ FAQ Lite covers this in section 23.7 in pretty good detail. I suggest reading that (and the rest of the FAQ) for a followup.

EDIT Corrected Most to All (thanks litb)

JaredPar
I wish I could upvote this 100 times. Doing such things usually indicates flawed design.
Kevin
Not most C++ implementations, but all C++ implementations have to call the current class's version. If some don't, then those have a bug :). I still agree with you that it's bad to call a virtual function from a base class - but semantics are precisely defined.
Johannes Schaub - litb
@litb, thanks for the clarification (updated).
JaredPar
+5  A: 

The C++ FAQ Lite Covers this pretty well:

Essentially, during the call to the base classes constructor, the object is not yet of the derived type and thus the base type's implementation of the virtual function is called and not the derived type's.

Aaron Maenpaa
+1  A: 

Do you know the crash error from Windows explorer?! "Pure virtual function call ..."
Same problem ...

class AbstractClass 
{
public:
    AbstractClass( ){
        //if you call pureVitualFunction I will crash...
    }
    virtual void pureVitualFunction() = 0;
};

Because there is no implemetation for the function pureVitualFunction() and the function is called in the constructor the program will crash.

TimW
+2  A: 

Calling a polymorphic function from a constructor is a recipe for disaster in most OO languages. Different languages will perform differently when this situation is encountered.

The basic problem is that in all languages the Base type(s) must be constructed previous to the Derived type. Now, the problem is what does it mean to call a polymorphic method from the constructor. What do you expect it to behave like? There are two approaches: call the method at the Base level (C++ style) or call the polymorphic method on an unconstructed object at the bottom of the hierarchy (Java way).

In C++ the Base class will build its version of the virtual method table prior to entering its own construction. At this point a call to the virtual method will end up calling the Base version of the method or producing a pure virtual method called in case it has no implementation at that level of the hierarchy. After the Base has been fully constructed, the compiler will start building the Derived class, and it will override the method pointers to point to the implementations in the next level of the hierarchy.

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;
}
// outputs: "Base" as the vtable still points to Base::f() when Base::Base() is run

In Java, the compiler will build the virtual table equivalent at the very first step of construction, prior to entering the Base constructor or Derived constructor. The implications are different (and to my likings more dangerous). If the base class constructor calls a method that is overriden in the derived class the call will actually be handled at the derived level calling a method on an unconstructed object, yielding unexpected results. All attributes of the derived class that are initialized inside the constructor block are yet uninitialized, including 'final' attributes. Elements that have a default value defined at the class level will have that value.

public class Base {
   public Base() { polymorphic(); }
   public void polymorphic() { 
      System.out.println( "Base" );
   }
}
public class Derived extends Base
{
   final int x;
   public Derived( int value ) {
      x = value;
      polymorphic();
   }
   public void polymorphic() {
      System.out.println( "Derived: " + x ); 
   }
   public static void main( String args[] ) {
      Derived d = new Derived( 5 );
   }
}
// outputs: Derived 0
//          Derived 5
// ... so much for final attributes never changing :P

As you see, calling a polymorphic (virtual in C++ terminology) methods is a common source of errors. In C++, at least you have the guarantee that it will never call a method on a yet unconstructed object...

David Rodríguez - dribeas
A: 

During the object's constructor call the virtual function pointer table is not completely built. Doing this will usually not give you the behavior you expect. Calling a virtual function in this situation may work but is not guaranteed and should be avoided to be portable and follow the C++ standard.

terry
+1  A: 

One solution to your problem is using factory methods to create your object.

  • Define a common base class for your class hierarchy containing a virtual method afterConstruction():
class Object
{
public:
  virtual void afterConstruction() {}
  // ...
};
  • Define a factory method:
template< class C >
C* factoryNew()
{
  C* pObject = new C();
  pObject->afterConstruction();

  return pObject;
}
  • Use it like this:

class MyClass : public Object { public: virtual void afterConstruction() { // do something. } // ... };

MyClass* pMyObject = factoryNew< MyClass >();

Tobias