Why is this not a call of a pure virtual function?
Asked Answered
R

2

11

I tried to "repair" the example in this answer as to demonstrate how a pure virtual function can be called.

#include <iostream>
using namespace std;

class A
{
    int id;
public:
    A(int i): id(i) {}
    int callFoo() { return foo(); }
    virtual int foo() = 0;
};

class B: public A
{
public:
    B(): A(callFoo()) {}
    int foo() { return 3; }
};

int main() {
    B b; // <-- this should call a pure virtual function
    cout << b.callFoo() << endl;
    return 0;
}

But I get no runtime error here (with C++ 4.9.2), but the output 3. I tried the same with Borland C++ 5.6.4, but there I'm getting an access violation. I think that foo() should be pure virtual in the call of the constructor of the base class.

Who is wrong? Should I try more compilers? Am I right in my understanding of virtual functions?

Roby answered 11/6, 2015 at 11:59 Comment(3)
I wouldn't use the results of a test with Borland C++ to see if a snippet of code is valid and/or compliant with the standard ;)Hypertension
I think you are dancing with undefined behavior. If you look at this example I get a segmentation fault when adding a cout statement to A::callFoo(). If we remove the cout statement then it compiles and works. I would have to guess that the minimal example just happens to work as the pointers stay aligned.Nomology
for anybody wondering, adding A *a = new B; gives a runtime error ideone.com/RV8mP8Bismuthinite
A
14

Your code has Undefined Behaviour: it is UB to call a member function on an object (even a non-virtual one) before all of its base classes have been initialised. C++14 (n4140) 12.6.2/14, emphasis mine:

Member functions (including virtual member functions, 10.3) can be called for an object under construction. Similarly, an object under construction can be the operand of the typeid operator (5.2.8) or of a dynamic_cast (5.2.7). However, if these operations are performed in a ctor-initializer (or in a function called directly or indirectly from a ctor-initializer) before all the mem-initializers for base classes have completed, the result of the operation is undefined. ...

ctor-initializer is the entire list following :. mem-initializer is one element of this list.

Avalos answered 11/6, 2015 at 12:11 Comment(4)
True and this UB rule is well-founded. But this only moves the headache - no compiler nor static code checker will find all errors. I was shocked that such simple inline cases didn't get instantly rejected.Roby
@Wolf: The compiler is not required to perform a full control-flow analysis, because that might not be possible to do statically at compile time.Ramification
@Ramification Implementing pure virtual functions to get rid of pure virtual function called messages seems like misuse to me, or was it designed for exactly this purpose?Roby
@Wolf: I'm not advocating the fix discussed in that post, I just thought it contained some interesting information. I'd be wary of implementing a pure virtual function because it would likely confuse someone.Ramification
L
5

The statement B b; calls the default constructor to B.

When constructing B, nothing pertinent to B is constructed until A is fully constructed.

So in attempting to call callFoo(), the behaviour is undefined since you cannot rely on the v-table for class B being set up.

In summary: the behaviour of calling a pure virtual function during the construction of an abstract class is undefined.

Lyle answered 11/6, 2015 at 12:8 Comment(1)
I guessed this would be the case because. So, it seems I was a bit to lazy with my "repair" suggestion.Roby

© 2022 - 2024 — McMap. All rights reserved.