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..
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With