Question

First example: pure virtual function foo() is called in the Base constructor with a non-virtual call. That explains why the code executes normally, i.e., it doesn't abort like the second example.

#include <iostream>
struct Base {
    Base() { foo(); }
    virtual void foo() = 0;
};

void Base::foo() { std::cout << "Base::foo()\n"; }

struct Derived : Base { void foo() { std::cout << "Derived::foo()\n"; } };

int main()
{
    Derived d;
}

Second example: here the pure virtual function foo() is also called in the Base ctor, but with a virtual call and the code aborts with R6025 - pure virtual function call.

#include <iostream>

struct Base {
    Base() { call_foo(); }
    virtual void foo() = 0;
    void call_foo() { foo(); }
};

void Base::foo() { std::cout << "Base::foo()\n"; }

struct Derived : Base { void foo() { std::cout << "Derived::foo()\n"; } };

int main()
{
    Derived d;
}

I know by §10.4/6 that pure virtual functions called from constructors or destructors is considered undefined behavior. But I'm curious to know what could be a reasonable explanation for the different calls to foo() in these two snippets?

Was it helpful?

Solution 2

As others have said, it is undefined behavior. What is (probably) happening, however, is that in the first case (the call in the constructor), the compiler knows the dynamic type of the object (since it is always the type being constructed), and so generates the call exactly as it would for calling any non-virtual function. (If you had failed to provide a definition, I imagine that the linker would have complained.) In the second case, you call the function from within another function. At that point, the compiler has no way of knowing the dynamic type the object will have at runtime; the function could have been called on a fully constructed object, perhaps of a derived type not even present in this source file. Since the compiler cannot determine statically the dynamic type the object will have at runtime, it must generate a call through the virtual function table (or whatever method it uses to resolve dynamic dispatch, but VS, like everyone else I know, uses a vptr to a vtable). Since calling a pure virtual function in this manner is undefined behavior, the compiler puts a pointer to an error handling routine in the vtable.

Note that in this case, the compiler could have seen that the call in the constructor of Base would resolve in a manner which causes undefined behavior, and generated code which would have triggered the error. Or... since all of the virtual functions of Base are pure virtual, the compiler could have known that the cannot be called during construction, and not even bothered to create a separate vtable for Base, perhaps initializing the vptr to point to the vtable of Derived before calling the constructor of Base. You can't count on anything such cases.

OTHER TIPS

In the first case, calling the function directly from the constructor, the dynamic type is known at compile time, so there's no need for virtual dispatch. The compiler can generate a direct call to Base::foo.

In the second case, calling it from another function which could be called at any time on any type derived from Base, the dynamic type is not known at compile time and so virtual dispatch is necessary.

As you say, this is undefined behaviour; in principle, anything could happen in either case. I would hope for a compiler warning in the first case (GCC gives one); but in the second case, the error is only detectable at runtime (if at all).

Well, undefined behavior means that anything may happen; including potentially surprising events like appearing to work or throwing an exception.

Now, given that VS chose to abort when a call to a pure-virtual is emitted during construction or destruction; I suspect that the first behavior (calling Base::foo) is actually a bug (according to their specifications). It would be so trivial for the code that devirtualize a function call to miss the special case of failing to compile if the called method is pure.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top