1

I wrote the following program with virtual functions:

struct A
{
    virtual void foo() = 0;
    A(){ init(); }
    void init(){ foo(); }
};

struct B : A
{
    virtual void foo(){ }
};

B a;

int main(){
    return 0;
}

DEMO

I thought some linker-error should be ccured becuase there's no implementation of the foo was found. We got runtime error instead. Why? Why not the linker error?

11
  • Missing functions do not require a linker error. It is undefined behaviour with no diagnostic required. It is just quality of implementation whether the linker actually pipes up or not. Commented Jun 30, 2015 at 4:34
  • Maybe it's just compiler strategy not to walk through all inheritance relationships to check pure virtual function implementation. Commented Jun 30, 2015 at 4:40
  • 1
    It is not an error to leave a pure virtual function undefined. It is a runtime error to call one. Commented Jun 30, 2015 at 4:42
  • In general case, it is not possible to check the targets of all dynamic (i.e. run-time dispatched) calls in the program at compile time. Which is why in general case it is not possible to determine whether a given pure virtual function is called or not. For this reason, it is not possible to diagnose such errors at compile time. Commented Jun 30, 2015 at 4:53
  • 2
    @n.m.: Not sure what you mean. The OP's code is one example of what is perfectly sufficient to fool a typical implementation. Note, BTW, that it does not matter whether there's a body provided for a pure virtual function. A virtual call to such function still leads to undefined behavior. The above code will still crash at run-time, even if you provide a body for A::foo(). Commented Jun 30, 2015 at 4:55

3 Answers 3

2

The first thing you have to understand here is that a call to foo() made while the constructor of class A is active is dispatched to A::foo(), even if the full object under construction has type B and B overrides foo(). The presence of B::foo() is simply ignored.

This means that your code attempts to call A::foo(). Since A::foo() is a pure virtual function, the behavior of your code is undefined.

C++ language make no guarantees of what kind of "error" should occur in such cases. Which means that your expectations of "linker error" are completely unfounded. If a programs makes an attempt to perform a virtual call to a pure virtual function the behavior is simply undefined. That is the only thing that can be said here from the C++ language point of view.

How this undefined behavior will manifest itself in practical implementations depends on the implementation. Undefined behavior is allowed to manifest itself through compile-time errors, for example.

In your case, your program attempts to make a virtual call to pure virtual function A::foo(). In general case the compiler dispatches virtual calls dynamically, through a run-time mechanism that implements polymorphism (so called virtual method table is the most popular one). In some cases, when compiler can determine the exact type of the object used in the call, it optimizes the code and makes an ordinary direct (non-dynamic) call to a virtual function.

In practice, if a function pure virtual, its virtual method table entry contains a null pointer. A dynamic call to such function typically leads to run-time error. Meanwhile, a direct (optimized) call to such function typically leads to a compiler or linker error.

In your example the compiler did not optimize the call. It made a full-fledged dynamic call to A::foo() through the virtual method table. The null pointer in that table triggered the run-time error.

If you call your pure virtual function directly from the constructor

 A() { foo(); } 

a typical compiler will normally make a direct (optimized) call to foo(), which will typically lead to a linker error.

Sign up to request clarification or add additional context in comments.

3 Comments

You mean the behavior is undefined if we call to a function from the constructor that is pure virtual in the constructor's class. We have no guarantee about producing warnings or something else. Is that why Scott Meyers suggest we don't call virtual function in cinstructors?
@Dmitry Bundin: The reason books suggest not to call virtual functions in constructors (or destructors) is what I stated above: when constructor (destructor) is active, the virtual dispatch mechanism works in the "truncated" version of the hierarchy: from the root to the class under construction. Everything below in the hierarchy is ignored as if it does not exist. Such "truncated" polymorphism can be useful, if one knows what one's doing. So, I would not be as aggressive as Scott Meyers is.
But for many newcomers to the language this "truncated" behavior is quite surprising, which is why it is often recommended to avoid virtual calls in constructors (and destructors). But again, once you understand how it works and know what you are doing, there's nothing wrong with using it in the code.
1

B does have an implementation of foo so there's no problem for the linker.

As far as I know, the fact that A is calling foo at a bad time is something the compiler/linker isn't required to figure out. (And although it might be simple to do such a check in this case, I'm sure we could come up with much more complicated cases that would be harder or perhaps impossible to catch.)

2 Comments

There's certainly a linker error when removing the curly braces defining the body of the empty function foo in the B struct: link
The OP's code above calls A::foo, not B::foo. Which is why it is irrelevant that B::foo is defined.
0

Your error is result of calling virtual functions from within constructors. The function called is the function in A, not more derived functions. C++ standard, section 12.7.4 states,

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

Now, you are cheating in your example. You are calling a normal function from your constructor and then a virtual function from your normal function. Change your code to,

struct A
{
    virtual void foo() = 0;
    A(){ foo(); }
};

and you'll get your error,

warning: pure virtual ‘virtual void A::foo()’ called from constructor [enabled by default]
_ZN1AC2Ev[_ZN1AC5Ev]+0x1f): undefined reference to `A::foo()'

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.