Linker error on pure virtual function call with gc

2019-02-14 02:14发布

问题:

A friend and I had a very interesting discussion about the construction of Objects that ended up with this piece of code:

#include <iostream>

class Parent {
  public:
    Parent( ) {
      this->doSomething( );
    }

    virtual void doSomething( ) = 0;
};

class Child : public Parent {
    int param;

  public:
    Child( ) {
      param = 1000;
    }

    virtual void doSomething( ) {
      std::cout << "doSomething( " << param << " )" << std::endl;
    }
};

int main( void ) {
    Child c;
    return 0;
}

I know that the standard does not define the behavior when a pure virtual function is called from a constructor or destructor, also this is not a practical example of how i would write code in production, it is just a test to check what the compiler does.

Testing the same construct in Java prints

doSomething( 0 )

That makes sense since param is not initialized at the point doSomething() is called from the parent constructor.

I would expect similar behavior in C++, with the difference that param contains anything at the time the function is called.

Instead compiling the above code results in a linker error with (c++ (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3) saying that the reference to Parent::doSomething( ) is undefined.

So, my question is: Why is this a linker error? If this is an error, I would expect the compiler to complain, especially because there is an implementation of the function. Any insight of how the linker works in this case or a reference to further reading would be highly appreciated.

Thank you in advance! I hope that this question is not a duplicate, but i could not find a similar question..

回答1:

So, my question is: Why is this a linker error? If this is an error, I would expect the compiler to complain, especially because there is an implementation of the function. Any insight of how the linker works in this case or a reference to further reading would be highly appreciated.

Let's expand this a bit more.

Why is it a linker error?

Because the compiler injected a call to Parent::doSomething() from the constructor, but the linker has found not definition of the function.

I would expect the compiler to complain, especially because there is an implementation of the function.

This is not correct. There is an override for that function that would be accessible through virtual dispatch, but the function Parent::doSomething() is not defined. There is a subtle but important difference there, that can be tested in a different way by disabling dynamic dispatch. You can disable dynamic dispatch for a particular call by qualifying the function with the class name, for example, in Child::doSomething() if you add Parent::doSomething(), that will generate a call to Parent::doSomething() without using dynamic dispatch to call the final overrider.

Why does this matter?

It matters because even if the function is pure-virtual (pure virtual means that dynamic dispatch will never dispatch to that particular overload), it can also be defined and called:

struct base {
   virtual void f() = 0;
};
inline void base::f() { std::cout << "base\n"; }
struct derived : base {
   virtual void f() {
      base::f();
      std::cout << "derived\n";
   }
};
int main() {
   derived d;
   d.f();        // outputs: base derived
}

Now, C++ has a separate compilation model, and that means that the functions need not be defined in this particular translation unit. That is Parent::doSomething() can be defined in a different translation unit that gets linked into the same program. The compiler cannot possibly know whether any other TU will define that function, only the linker knows, and thus it is the linker the one that complains.

Any insight of how the linker works in this case or a reference to further reading would be highly appreciated.

As stated before, the compiler (this particular compiler) is adding a call to the particular override at the Parent level. The linker as in all other function calls is trying to find that symbol defined in any of the translation units and is failing, thus triggering the error.

The pure-virtual specifier has as a sole purpose avoiding the implicit use (odr-use in the standard) of the function when creating the virtual table. That is, the only purpose of the pure specifier is not to add a dependency on that symbol to the virtual table. That in turn means that the linker will not require the presence of the symbol (Parent::doSomething) in the program for the purpose of dynamic dispatch (vtable), but it will still require that symbol if it is explicitly used by the program.



回答2:

The compiler knows that when you call doSomething from inside the constructor, that call must reference doSomething in this class, even if it is virtual. So it will optimize away the virtual dispatch and instead just do a normal function call. Since the function is not defined anywhere, this results in an error at link-time.



回答3:

At the pointer where doSomething() is called, the compiler can be certain that the dynamic type of *this is Parent, so there is no need to call the function indirect. This behavior highly depends on the compiler/linker you use.



标签: c++ linker