5

Consider the following code:

#include <iostream>
using namespace std;

class A
{
  public:
   virtual void f() = 0;
   A(){f();}
};

void A::f() {
    cout<<"A"<<endl;
}

class B:public A{
 public:
    void f(){cout<<"B"<<endl;}
};
int main()
{
 B b;
}

In this case I directly call the virtual function from constructor and get compiler warning which says:
warning: abstract virtual 'virtual void A::f()' called from constructor.
But it executes without termination and prints A.

If I wrap the call of the function like this:

class A
{
  public:
   virtual void f() = 0;
   A(){g();}
   void g(){f();}
};

void A::f(){cout<<"A"<<endl;}

class B:public A{
 public:
    void f(){cout<<"B"<<endl;}
};
int main()
{
 B b;
}

The compiler does not output any warning during compilation but it crushes at runtime with the following message:

pure virtual method called   
terminate called without active exception   
Abort

Can anybody explain the behavior of both of this cases?

dragosht
  • 3,237
  • 2
  • 23
  • 32
Eduard Rostomyan
  • 7,050
  • 2
  • 37
  • 76

4 Answers4

1

In the first case, the compiler happens to save you by statically dispatching to A::f(), since it knows the static type of A. But it's quite right that this is horribly undefined behaviour and you shouldn't do it.

In the second case, the compiler does not statically dispatch to A::f() since the call is not in the constructor so it must dynamically dispatch it. Different ABIs handle pure virtual calls differently, but both MSVC and Itanium have a dedicated pure virtual call handler which is placed in the vtable to catch these events. This is what produces the error message you see.

Puppy
  • 144,682
  • 38
  • 256
  • 465
1

§ 10.4 Abstract classes [class.abstract] / p6

Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call (10.3) to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined.

In brief: The effect of making a call to a pure virtual function directly or indirectly for the object being created from constructor is undefined.

A call to pure virtual member functions cannot be used from a constructor or a destructor, no matter if the call is direct or indirect, because then you end up with an undefined behavior.

The only useful example of providing the implementation of a pure virtual function is when calling it from a derived class:

struct A
{
    virtual void f() = 0;
};

void A::f()
{
    cout<<"A"<<endl;
}

struct B : A
{
    void f()
    {
        A::f();
        cout<<"B"<<endl;
    }
};
Community
  • 1
  • 1
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
1

From a compiler's point of view, if you look at how the function f() is invoked:

  • Case-1: A's ctor calls A-ctor => f() directly. Compiler knows precisely that this is the case and decides to issue a warning.
  • Case-2: A's ctor calls A-ctor => g() => f(). There are entirely legitimate cases of calling f() from one of the class methods. Compiler can not say that this is illegal. The callgraph could have been from * => bar() => g() -> f(), meaning the type of the object is not known. Such paths being possible, makes dynamic dispatching necessary - leading to the runtime error.

As others pointed out, this is undefined usage and compilers only go so far in detecting and warning.

KalyanS
  • 527
  • 3
  • 8
0

Undefined behaviour means that the compiler does not have to handle the situation in any particularly defined manner.

Here your compiler, that knew the actual type of A in its constructor was able to inline in the pure virtual method rather than call it through the v-table. This is what would happen if the method were normal virtual, not pure virtual, and this would be defined behaviour.

Whilst it would be the behaviour too even via g(), the compiler did not do this for a pure virtual f() function. It doesn't have to.

The simple moral is do not invoke undefined behaviour, and if you want to call f() from the constructor do not make it pure virtual.

If you want to enforce your sub-classes to implement f(), do not call it from the constructor of A but give that function you want to call a different name. Preferably not virtual at all.

CashCow
  • 30,981
  • 5
  • 61
  • 92