3

Without the line marked BODY, I know this is not safe. But with it, is this safe?

struct A
{
    virtual ~A() { f(); }

    virtual void f() = 0;
};

void A::f() {} // BODY

struct B : A
{
    void f() {}
};

int main()
{
    delete new B;
}

Working example: http://ideone.com/9bRZ3i

Neil Kirk
  • 21,327
  • 9
  • 53
  • 91
  • What do you mean by "Concerns about virtual lookup behaving differently in constructors/destructors aside"? That's sort of an elephant in the room, no? – Potatoswatter Aug 27 '13 at 03:47
  • @Potatoswatter I know that when you call a virtual function in A, it calls A's virtual function and not B, which is not what some people expect. I didn't want to lose focus on that. I will remove the statement as it is confusing. – Neil Kirk Aug 27 '13 at 03:49
  • I didn't even know that you can provide a function body for a pure virtual member method. What's the point of that and when is it called? Only from ctor or dtor? – Walter Aug 27 '13 at 12:59
  • @Walter There are two uses. The first is when providing a pure virtual destructor, a body must be provided. The second is to provide a "default" implementation for a function, that still must be overridden. The derived class's function may choose to call the pure virtual function if it wishes. – Neil Kirk Aug 27 '13 at 13:24

2 Answers2

7

No, that is not safe. While the A constructor (or destructor) is executing the object is of type A, not yet (not anymore) a B object. The call to f() will attempt to dispatch to the (still) pure virtual function and cause undefined behavior. Most implementations will catch this and terminate the application with an error message that indicates that a pure virtual function was called.


After the edit:

The fact that there is a definition for the pure virtual function means that it is legal to call it without going through virtual dispatch. It is still illegal to call a pure virtual function using dynamic dispatch. But you could rewrite the constructor as:

A::~A() { A::f(); }  // qualification disables dynamic dispatch

Without dynamic dispatch, the code becomes valid.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 1
    It's the destructor and not the constructor that calls the pure virtual function. – Some programmer dude Aug 27 '13 at 03:44
  • I added a working example. Could you explain why the behavior is undefined? – Neil Kirk Aug 27 '13 at 03:44
  • 1
    @NeilKirk: it's undefined because that's what the standard says. C++11, 10.4/6, "the effect of making a virtual call to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined." – Mike Seymour Aug 27 '13 at 03:52
  • @MikeSeymour Is there a reason it's not allowed? – Neil Kirk Aug 27 '13 at 03:54
  • @MikeSeymour: I think that is ruled out by 12.7p4 `the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class.` There is **no** virtual call and therefore no UB. – Jesse Good Aug 27 '13 at 03:57
  • @NeilKirk: You mean, is there a rationale for the standard not defining the behaviour? I don't know, but my guess would be that defining the behaviour would force the compiler to generate a vtable for the abstract class, which otherwise wouldn't be needed. – Mike Seymour Aug 27 '13 at 03:58
  • @JesseGood: It is valid to call the function directly, but not through dynamic dispatch. It works in some cases as the compiler can avoid the virtual dispatch, but that is not guaranteed, and you will see compilers that go both ways. It is undefined as the standard does not require that the virtual dispatch needs to be statically dispatched when the compiler knows which is the final overrider. – David Rodríguez - dribeas Aug 27 '13 at 04:09
  • @DavidRodríguez-dribeas See my answer. Unless there's something in the Standard to forbid the dispatch (and I sort of want there to be), it's well-defined behavior. – Potatoswatter Aug 27 '13 at 04:10
  • @Potatoswatter: 10.4/6 (quoted in my comment above) says that it's undefined; but 12.7/4 (quoted in your answer) appears to define it. So I guess the answer is "maybe". – Mike Seymour Aug 27 '13 at 04:13
  • @Potatoswatter: I updated the last comment. It is still undefined behavior. The quote you provide means that dynamic dispatch cannot dispatch to a type that is below in the hierarchy, but that does not mean that the compiler must not use the dynamic dispatch mechanism. The function is pure virtual, and thus the entry in the vtable won't refer to `A::f`, a call that uses the dynamic dispatch will go through an invalid entry in the vtable and cause undefined behavior... or not if the compiler turns the dynamic dispatch into static dispatch as an optimization, but that is not required. – David Rodríguez - dribeas Aug 27 '13 at 04:13
  • Inside the `~A()` is `f();` virtual dispatch or equivalent to `A::f();`? – Neil Kirk Aug 27 '13 at 04:16
  • @DavidRodríguez-dribeas Ugh, I updated my answer; it is specifically disallowed. The Committee is merciful. What mechanism is used is immaterial; the question is whether dynamic dispatch is allowed to find a pure virtual function. – Potatoswatter Aug 27 '13 at 04:16
  • @Potatoswatter why do you prefer it to be disallowed? It's not disallowed for regular virtual functions, so why should pure virtual functions with bodies be disallowed? In other words, if you disallow one of them, why not disallow the other? – Neil Kirk Aug 27 '13 at 04:17
  • @NeilKirk Because of the principle, which I put in my answer earlier and removed (but should restore now) that pure-virtual hides the function from dynamic dispatch. The reason `= 0` is the syntax is because you're "zeroing out" the entry in the vtable. – Potatoswatter Aug 27 '13 at 04:18
  • @Potatoswatter No, `= 0` does not mean anything is zeroed out! That is a myth! The syntax means nothing. They just didn't have time to add a new keyword such as `abstract` to the language. Really! – Neil Kirk Aug 27 '13 at 04:25
  • @NeilKirk: Conceptually I have always consider that pure virtual functions are never overriders, in the sense that dynamic dispatch will never pick that as the *final-overrider* for anything. I have tried to check and recheck in the standard where I got that from, but I cannot support it, so take this as just opinion :) At any rate, move the call to a different function, call that from the constructor/destructor and you will see that the *indirectly* makes all the sense in the world (at least by means of the termination of your program). – David Rodríguez - dribeas Aug 27 '13 at 04:27
  • @NeilKirk Syntax is syntax, just a way to fool us humans into outputting ASCII. But it bears significant resemblance to how the same thing is done in C, and it reveals something meaningful about C++'s early heritage. – Potatoswatter Aug 27 '13 at 04:38
  • @DavidRodríguez-dribeas I finally found it; see my update. Edit, oh woops, that's just what Mike said much earlier. Wish I'd read that comment. – Potatoswatter Aug 27 '13 at 04:38
4

If you want to bypass virtual dispatch and call the function body you have defined, you must qualify the function name:

virtual ~A() { A::f(); } // OK.

Otherwise, the call will initiate virtual dispatch, but only to the base class, because the object of derived type has already been destroyed before its bases.

C++11 §12.7/4 directly addresses your question:

Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class’s non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class. If the virtual function call uses an explicit class member access (5.2.5) and the object expression refers to the complete object of x or one of that object’s base class subobjects but not x or one of its base class subobjects, the behavior is undefined.

However, §10.4/6 forbids doing this with a pure virtual function:

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.

So, it's UB.

The effect of "pure virtual" is to hide the function definition from virtual lookup. You will never reach the definition of a pure virtual function from a dynamic dispatch function call, except perhaps as an effect of undefined behavior.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • I am not convinced that calling a function without qualification in the constructor/destructor does so virtually. We shall see.. http://stackoverflow.com/questions/18456695/if-a-virtual-function-is-called-from-a-constructor-destructor-without-qualificat – Neil Kirk Aug 27 '13 at 04:00
  • @NeilKirk: Within the constructor, the compiler can, and probably will, optimise it to a non-virtual call, but there's no requirement to do that. If it's called indirectly (i.e. the constructor calls some other function, which calls the virtual function), then it will more likely have to be called virtually. – Mike Seymour Aug 27 '13 at 04:03
  • @NeilKirk I live under the illusion that adding more C++ won't displace the C++ I already know from inside my head. So, it's my pleasure :) . – Potatoswatter Aug 27 '13 at 04:20