Pure Virtual Function call from Base Ctor
Asked Answered
L

6

5

Consider the following sample code:

#include <iostream>

using namespace std;

class base
{
   public:
      base()
      {
         bar(); //Line1
         this->bar(); //Line2
         base *bptr = this; 
         bptr->bar(); //Line3
         ((base*)(this))->bar(); //Line4
      }

      virtual void bar() = 0;
};

class derived: base
{
   public:
      void bar()
      {
         cout << "vfunc in derived class\n";
      }
};

int main()
{
   derived d;
}

The above code has pure virtual function bar() in base class which is overriden in the derived class. The pure virtual function bar() has no definition in base class.

Now focus on Line1, Line2, Line3 and Line4.

I understand : Line1 gives compilation error, because pure virtual function cannot be called from ctor.

Questions:

  1. Why does Line2 and Line4 give no compilation error for the same reason mentioned in I understand statement above?. The calls in Line2 and Line4 will eventually cause linker-error only.

  2. Why does Line3 give neither compilation error nor linker error but gives run-time exception only ?

Real-Life example of UB when Pure virtual function call through constructor:

Real-Life example of UB when Pure virtual function call through constructor

Logue answered 9/2, 2012 at 14:1 Comment(4)
Can you post compiler errors after removing the first call ?Beulahbeuthel
Short answer: you should not call a virtual function in the ctor because you do not know what side effects it has; it could deadlock or worse.Acetaldehyde
@Adrian: You should not call a pure virtual function, because that gives undefined behaviour. Calling a non-pure virtual function has well-defined behaviour.Forepart
@ Dani, there is no compiler error if the first call Line1 is removed. There is only linker errorLogue
A
5

Calling an Pure virtual function from constructor is an Undefined Behavior & the compiler is free to show any behavior.

Reference:
C++03 Standard 10.4/6:

"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."

The C++ standard defines Undefined behavior in:

[defns.undefined] 1.3.12 undefined behavior

behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements. Undefined behavior may also be expected when this International Standard omits the description of any explicit definition of behavior. [Note: permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. ]

Anglonorman answered 9/2, 2012 at 14:12 Comment(7)
@ Als: this in base ctor is of type base*.Also bptr is of type base*. Then why do this->bar() and bptr->bar() differ?Logue
@LinuxPenseur: Well to be honest I don't know and I doubt If anyone other than the ones who wrote the compiler code for gnu gcc will be able to answer that with 100% authority. Having said that As per the Standard reference quoted in my answer the compiler is free to show any behavior in this case. So while it maybe a Q of curiosity that why 1 issues error but not 2, as per the standard the compiler is free to show either of the behaviors and it is still 100% compliant.Anglonorman
@ Als : I got your point. I have updated the question with the after effects of Line3 in real life application, just to highlight the question. Please do check the question again:)Logue
@ Als : I was not able to upload the image :(Logue
@LinuxPenseur: I am not sure,what image or what real life application you wanted to demonstrate but the fact is this behavior of the compiler is perfectly valid and allowed and that is what the quotes i posted tell you.If you are going to write any code relying on compilers behavior towards an UB then the code is invalid and you are asking for trouble.Anglonorman
@ Als: Sometime back firefox had a bug because of PVF call from ctor. I was trying to upload the image of firefox showing the run time exception just to add more flavour to the question:)Logue
@LinuxPenseur: There you go I added it for you.Anglonorman
F
6

In all four cases, the behaviour is undefined; so exactly what happens depends on what your compiler happens to do in the face of invalid input.

The compiler might attempt to diagnose the problem to give a warning; this is easy to do for Line 1, and more difficult for the other lines, which would explain why you only see a warning for Line 1.

When calling a virtual function from a constructor, the compiler knows which overload should be called, and so it might generate a static call. This is why you get a link error from Line 2 and Line 4.

In Line 3, the compiler must have decided that it's too difficult to work out whether it can generate a static call, so it generated a dynamic call instead. Tracking the value of a variable is rather harder than working out that a temporary pointer must refer to this, and often not possible at all. That's why you get a run-time error there.

Of course, all of this is undefined behaviour, and might change from compiler to compiler, or according to the phase of the moon.

If the function had an implementation, then it would be valid to call it statically, as Base::bar(), or bptr->Base::bar(). Calling it dynamically would still give undefined behaviour.

Forepart answered 9/2, 2012 at 14:10 Comment(3)
How is it more difficult in line 2? I would, in fact, expect a compiler to rewrite line 1 into line 2 at a fairly early stage of compilation---almost as soon as it has bound the name bar.Lysander
@JamesKanze: I don't know exactly how it's more difficult, but GCC certainly thinks so, since it warns for Line 1 but not Line 2. I don't know enough about compilers to speculate further.Forepart
@LokiAstari Line 1 rewrites to line 2. Both have exactly the same semantics. Both require dynamic resolution, but in both cases, the context is such that the compiler knows the exact results of the dynamic resolution. (The usual way of implementing line 1 in the compiler is to rewrite it as line 2.) And while the reason why this is undefined behavior is because you can't diagnose it in general, when lexically in the constructor, the compiler knows the dynamic type of what this points to, and can easily diagnose the problem.Lysander
A
5

Calling an Pure virtual function from constructor is an Undefined Behavior & the compiler is free to show any behavior.

Reference:
C++03 Standard 10.4/6:

"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."

The C++ standard defines Undefined behavior in:

[defns.undefined] 1.3.12 undefined behavior

behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements. Undefined behavior may also be expected when this International Standard omits the description of any explicit definition of behavior. [Note: permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. ]

Anglonorman answered 9/2, 2012 at 14:12 Comment(7)
@ Als: this in base ctor is of type base*.Also bptr is of type base*. Then why do this->bar() and bptr->bar() differ?Logue
@LinuxPenseur: Well to be honest I don't know and I doubt If anyone other than the ones who wrote the compiler code for gnu gcc will be able to answer that with 100% authority. Having said that As per the Standard reference quoted in my answer the compiler is free to show any behavior in this case. So while it maybe a Q of curiosity that why 1 issues error but not 2, as per the standard the compiler is free to show either of the behaviors and it is still 100% compliant.Anglonorman
@ Als : I got your point. I have updated the question with the after effects of Line3 in real life application, just to highlight the question. Please do check the question again:)Logue
@ Als : I was not able to upload the image :(Logue
@LinuxPenseur: I am not sure,what image or what real life application you wanted to demonstrate but the fact is this behavior of the compiler is perfectly valid and allowed and that is what the quotes i posted tell you.If you are going to write any code relying on compilers behavior towards an UB then the code is invalid and you are asking for trouble.Anglonorman
@ Als: Sometime back firefox had a bug because of PVF call from ctor. I was trying to upload the image of firefox showing the run time exception just to add more flavour to the question:)Logue
@LinuxPenseur: There you go I added it for you.Anglonorman
S
1

I can partially answer. Line 3 requires that the compiler do data flow analysis to determine that the function Is not being called on another fully constructed object.

Spectroheliograph answered 9/2, 2012 at 14:11 Comment(0)
V
1

Which compiler are you using?

Vc10 and gcc 4.6 all compile fine. gcc give a nice warning about calling virtual function from constructor will be just using the base::bar() function instead of polymorphic.

This is probably bocs calling virtual from constructor is Undefined Behavior.

Vacla answered 9/2, 2012 at 14:17 Comment(0)
O
0

Weird C++ fact: it is legal (and, very rarely, useful) to define a pure virtual function.

You could try adding

void base::bar() { cout << "Wuh?"; }

and you will find line 2 and line 4 invoke that function.

The legal way to call that definition is to do it explicitly: base::bar();

The compiler has managed to optimise the virtual call (which will fail) to the non-virtual call in those cases. It is neither required nor prevented from doing that; all your calls have undefined behaviour.

Occlusive answered 9/2, 2012 at 14:13 Comment(0)
L
0

Your code contains undefined behavior, so whatever the compiler does is correct. All of the calls to bar() in your code require dynamic resolution, and would result in a call to a pure virtual function; this is undefined behavior. (You can call Base::bar() if you write it like that and the function exists, since no dynamic resolution is required.) The fact that the code compiles doesn't mean that it will run successfully; in the case of g++, for example, I'm fairly sure that it will crash with an error message.

Whether the compiler complains or not probably depends on how much effort it goes to to resolve the dynamic resolution at compile time. Without optimization, it almost certainly can't resolve 3 at compile time, but I'm somewhat surprised that it treats 1 and 2 differently.

And the statement that "pure virtual function cannot be called from constructor" is false. The only time there is a problem is when dynamic resolution resolves to a pure virtual function. Calling it with static resolution (assuming it exists) is fine, and calling a pure virtual function in a base class is fine if the dynamic resolution turns up a non pure virtual function in a derived class whose constructor has started or has run.

Lysander answered 9/2, 2012 at 14:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.