Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why a virtual call to a pure virtual function from a constructor is UB and a call to a non-pure virtual function is allowed by the Standard?

From 10.4 Abstract Classes parag. 6 in the Standard :

"Member functions can be called from a constructor (or destructor) of an abstract class; 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."

Assuming that a call to a non-pure virtual function from a constructor (or destructor), is allowed by the Standard, why the difference ?

[EDIT] More standards quotes about pure virtual functions:

§ 10.4/2 A virtual function is specified pure by using a pure-specifier (9.2) in the function declaration in the class definition. A pure virtual function needs be defined only if called with, or as if with (12.4), the qualified-id syntax (5.1). ... [ Note: A function declaration cannot provide both a pure-specifier and a definition —end note ]

§ 12.4/9 A destructor can be declared virtual (10.3) or pure virtual (10.4); if any objects of that class or any derived class are created in the program, the destructor shall be defined.

Some questions that need answering are:

  • Where the pure virtual function has not been given an implementation, should this not be a compiler or linker error instead?

  • Where the pure virtual function has been given an implementation, why can it not be well-defined in this case to invoke this function?

like image 341
Belloc Avatar asked Feb 08 '12 00:02

Belloc


People also ask

Can you call a pure virtual function in a constructor?

Pure virtual functions must not be called from a C++ constructor. As a general rule, you should never call any kind of virtual function in a constructor or destructor because those calls will never go to a more derived class than the currently executing constructor or destructor.

Why should calls to virtual functions be avoided in constructors and destructors?

So, Don't invoke virtual functions from constructors or destructors that attempts to call into the object under construction or destruction, Because the order of construction starts from base to derived and the order of destructors starts from derived to base class.

Why we use virtual function and pure virtual function?

A virtual function is a member function of base class which can be redefined by derived class. A pure virtual function is a member function of base class whose only declaration is provided in base class and should be defined in derived class otherwise derived class also becomes abstract.

Why virtual function is called Virtual?

'virtual function' means a member function where the specific implementation will depend on the type of the object it is called upon, at run-time. The compiler and run-time support of the language contrive to make this happen. The keyword 'virtual' in C++ was taken from Simula, which had impressed Bjarne Stroustrup.


2 Answers

Because a virtual call can NEVER call a pure virtual function -- the only way to call a pure virtual function is with an explicit (qualified) call.

Now outside of constructors or destructors, this is enforced by the fact that you can never actually have objects of an abstract class. You must instead have an object of some non-abstract derived class which overrides the pure virtual function (if it didn't override it, the class would be abstract). While a constructor or destructor is running, however, you might have an object of an intermediate state. But since the standard says that trying to call a pure virtual function virtually in this state results in undefined behavior, the compiler is free to not have to special case things to get it right, giving much more flexibility for implementing pure virtual functions. In particular, the compiler is free to implement pure virtuals the same way it implements non-pure virtuals (no special case needed), and crash or otherwise fail if you call the pure virtual from a ctor/dtor.

like image 84
Chris Dodd Avatar answered Oct 31 '22 17:10

Chris Dodd


I think this code is an example of the undefined behaviour referenced by the standard. In particular, it is not easy for the compiler to notice that this is undefined.

(BTW, when I say 'compiler', I really mean 'compiler and linker'. Apologies for any confusion.)

struct Abstract {
    virtual void pure() = 0;
    virtual void foo() {
        pure();
    }
    Abstract() {
        foo();
    }
    ~Abstract() {
        foo();
    }
};

struct X : public Abstract {
    virtual void pure() { cout << " X :: pure() " << endl; }
    virtual void impure() { cout << " X :: impure() " << endl; }
};
int main() {
    X x;
}

If the constructor of Abstract directly called pure(), this would obviously be a problem and a compiler can easily see that there is no Abstract::pure() to be called, and g++ gives a warning. But in this example, the constructor calls foo(), and foo() is a non-pure virtual function. Therefore, there is no straightforward basis for the compiler or linker to give a warning or error.

As onlookers, we can see that foo is a problem if called from the constructor of Abstract. Abstract::foo() itself is defined, but it tries to call Abstract::pure and this doesn't exist.

At this stage, you might think that the compiler should issue a warning/error about foo on the grounds that it calls a pure virtual function. But instead you should consider the derived non-abstract class where pure has been given an implementation. If you call foo on that class after construction (and assuming you haven't overriden foo), then you will get well-defined behaviour. So again, there is no basis for a warning about foo. foo is well-defined as long as it isn't called in the constructor of Abstract.

Therefore, each method (the constructor and foo) are each relatively OK if you look on them on their own. The only reason we know there is a problem is because we can see the big picture. A very smart compiler would put each particular implementation/non-implementation into one of three categories:

  • Fully-defined: It, and all the methods it calls are fully-defined at every level in the object hierarchy
  • Defined-after-construction. A function like foo that has an implementation but which might backfire depending on the status of the methods it calls.
  • Pure virtual.

It's a lot of work to expect a compiler and linker to track all this, and hence the standard allows compilers to compile it cleanly but give undefined behaviour.

(I haven't mentioned the fact that it is possible to give implementations to pure-virtual methods. This is new to me. Is it defined properly, or is it just a compiler-specific extension? void Abstract :: pure() { })

So, it's not merely undefined 'because the standard says so`. You have to ask yourself 'what behaviour would you define for the above code?'. The only sensible answer is either to leave it undefined or to mandate a run-time error. The compiler and linker won't find it easy to analyse all these dependencies.

And to make matters worse, consider pointers-to-member-functions! The compiler or linker can't really tell if the 'problematic' methods will ever be called - it might depend on a whole load of other things that happen at runtime. If the compiler sees (this->*mem_fun)() in the constructor, it can't be expected to know how well-defined mem_fun is.

like image 34
Aaron McDaid Avatar answered Oct 31 '22 19:10

Aaron McDaid