A friend and I had a very interesting discussion about the construction of Objects that ended up with this piece of code:
#include
class Pa
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.
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.
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.