Search code examples
c++inheritancepolymorphismvirtual-functionslate-binding

Does destructing objects stop the late binding?


Consider the following C++ classes inheritance hierarchy and their intended polymorphic behaviour.

#include <iostream>
using namespace std;

class A
{
public:
  A () { cout << "A constructor\n"; }
  virtual void display() { cout << "A display\n"; }
  virtual ~A() { cout << "A destructor\n"; }
  friend ostream& operator << (ostream &out, A &a) {
    a.display();
    return out;
  }
};

class B : public A
{
public:
  B () { cout << "B constructor\n"; }
  virtual void display() { cout << "B display\n"; }
  virtual ~B() { cout << "B destructor\n"; }
};

class C : public B
{
public:
  C () { cout << "C constructor\n"; }
  virtual void display() {cout << "C display\n";}
  virtual ~C() { cout << "C destructor\n"; }
};
int main()
{
  C c1;
  cout << endl; c1.display(); cout << endl;
  c1.~C();
  cout << endl; c1.display(); cout << endl;
  c1.~C();

  cout << "=================================================" << endl;

  C c2;
  cout << endl << c2 << endl;
  c2.~C();
  cout << endl << c2 << endl;
  c2.~C();

  return 0;
}

My understanding is that the display member function is virtual and therefore will always behave as such. In the top part of the main program it behaves well; that is when we call c1.display() it prints "C display" message. This is true even after destructing the c1 object.

In the lower part, we don't call the display function. Instead we call the output stream friend function which will in turn call the display member function. In this case, it works perfectly well at first; that is cout << endl << c2 << endl; prints C display. This is expected because we are passing the c1 object to the ostream friend function by reference and therefore late binding will take care of which display member function to execute.

But then when we do the same cout << endl << c2 << endl; after destructing c2 object, strangely the display member function is no more behaving as expected. It calls the base class display and prints "A display" message.

I don't understand it? Does destructing objects stop the late binding?


Solution

  • Destroying an object makes it illegal to continue calling member functions of that object. Your code's behaviour is undefined, and whatever behaviour you are observing cannot be relied upon to persist across even runs of the same program, let alone different compiler flags or entirely different implementations.

    However, we can guess why you are seeing the particular behaviour you report, with the understanding that we should not continue to compile and run such code in the future as its behaviour is not guaranteed.

    It is likely that your compiler is devirtualizing the c1.display() call since it can see the definition of c1 (and thus knows that its dynamic type is C). Thus, the generated code likely does not consult the vtable at all. This explains why you continue to see "C display" even though the object has already been destroyed.

    In the c2 case, the object is being passed by reference and the compiler is probably not inlining aggressively enough to devirtualize the eventual display call. If it consults the vtable after c2 is destroyed, it probably finds the vtable for A, because the destruction process of the C object must eventually reset the A subobject's vptr to point to the A vtable prior to the execution of A::~A (in case A::~A calls any virtual functions). So that explains the "A display" message.