A pure virtual function raises interesting cases

Consider the following code:

#include <iostream> using namespace std; class A { public: virtual void f() = 0; A(){f();} }; void A::f() { cout<<"A"<<endl; } class B:public A{ public: void f(){cout<<"B"<<endl;} }; int main() { B b; } 

In this case, I directly call the virtual function from the constructor and get a compiler warning saying:
warning: abstract virtual virtual void A :: f () 'called from the constructor.
But it runs without completion and prints A.

If I end the function call as follows:

 class A { public: virtual void f() = 0; A(){g();} void g(){f();} }; void A::f(){cout<<"A"<<endl;} class B:public A{ public: void f(){cout<<"B"<<endl;} }; int main() { B b; } 

The compiler does not display any warnings at compile time, but it crashes at runtime with the following message:

 pure virtual method called terminate called without active exception Abort 

Can someone explain the behavior of both of these cases?

+5
source share
4 answers

Β§ 10.4 Abstract classes [class.abstract] / p6

Member functions can be called from the constructor (or destructor) of an abstract class; the effect of a virtual call (10.3) on a pure virtual function directly or indirectly for the created (or destroyed) object from the constructor (or destructor) - undefined .

In short: The effect of invoking a pure virtual function directly or indirectly for an object created from the constructor is undefined.

A call to pure virtual member functions cannot be used from a constructor or destructor, regardless of whether the call is direct or indirect, because then you find yourself in undefined behavior.

The only useful example of providing an implementation of a pure virtual function is calling it from a derived class:

 struct A { virtual void f() = 0; }; void A::f() { cout<<"A"<<endl; } struct B : A { void f() { A::f(); cout<<"B"<<endl; } }; 
+1
source

In the first case, the compiler saves you by statically sending to A::f() , because it knows the static type of A But it’s absolutely true that this is terrible undefined behavior, and you should not do this.

In the second case, the compiler does not statically send to A::f() , since the call is not in the constructor, so it must dynamically send it. Different ABIs handle pure virtual calls differently, but both MSVC and Itanium have a dedicated pure virtual call handler that is placed in a vtable to catch these events. This is what you see error message.

+1
source

From a compiler point of view, if you look at how the f () function is called:

  • Case-1: ctor calls A-ctor => f () directly. The compiler knows for sure that this is the case and decides to issue a warning.
  • Case 2: ctor calls A-ctor => g () => f (). There are quite legitimate cases of calling f () from one of the methods of the class. The compiler cannot say that it is illegal. Callgraph could be from * => bar () => g () β†’ f (), which means that the type of the object is unknown. Such paths that are possible make dynamic dispatching necessary - leading to runtime errors.

As others have pointed out, this is undefined use and compilers only still detect and warn.

+1
source

Undefined behavior means that the compiler should not handle the situation in any particular way.

Here, your compiler, which knew the actual type A in its constructor, was able to embed it in a pure virtual method, rather than calling it via a v-table. This is what will happen if the method is normal virtual and not pure virtual, and this behavior is determined.

Although this would be behavior even through g() , the compiler did not do this for a pure virtual f() function. It's not obligatory.

Simple morality does not cause undefined behavior, and if you want to call f() from the constructor, do not make it pure virtual.

If you want to use your subclasses to implement f() , do not call it from constructor A, but provide this function, which you want to call a different name. Usually not virtual.

0
source

Source: https://habr.com/ru/post/1202067/


All Articles