This seems to be a fundamental question, but I did not see it asked:
Suppose the following simple case:
No virtual members.
Virtual inheritance is used to allow multiple paths to the same base.
What is the price of virtual inheritance in terms of the time needed to access the members of the most derived class? In particular, if there is a non-zero price, does it pertain only to the members that are inherited through more than one path or to other members as well?
What is the price of virtual inheritance in terms of the time needed to access the members of the most derived class?
one offset-lookup and an add (2 instructions and a memory fetch)
In particular, if there is a non-zero price, does it pertain only to the members that are inherited through more than one path or to other members as well?
Yes, and even then not always. If the compiler has enough information to prove that the access does not need to be via indirection, it is free to short-circuit the lookup at compile time.
It'd probably be good to clarify exact when this would be the case. – Nicol Bolas
Well said sir.
Here is an example to demonstrate this. Compile with -O2 and -S options to see the optimisation in action.
#include <memory>
#include <string>
enum class proof {
base,
derived
};
// volatile forces the compiler to actually perform reads and writes to _proof
// Without this, if the compiler can prove that there is no side-effect of not performing the write,
// it can eliminate whole chunks of our test program!
volatile proof _proof;
struct base
{
virtual void foo() const {
_proof = proof::base;
}
virtual ~base() = default;
};
struct derived : base
{
void foo() const override {
_proof = proof::derived;
}
};
// factory function
std::unique_ptr<base> make_base(const std::string&name)
{
static const std::string _derived = "derived";
// only create a derived if the specified string contains
// "derived" - on my compiler this is enough to defeat the
// optimiser
if (name == _derived) {
return std::make_unique<derived>();
}
else {
return {};
}
}
auto main() -> int
{
// here the compiler is fully aware that p is pointing at a derived
auto p = std::make_unique<derived>();
// therefore the call to foo() is made directly (in fact, clang even inlines it)
p->foo();
// even here, the compiler 'knows' that b is pointing at a 'derived'
// so the call to foo is made directly (and indeed on my compiler, completely
// inlined)
auto b = std::unique_ptr<base>(new derived);
b->foo();
// here we assign a derived to b via indirect construction through a string.
// Unless the compiler is going to track this string and follow the logic in make_base
// (and on my compiler it does not) this will prevent the virtual call to foo() from
// being turned into a direct call.
// Therefore, this call will be made via the virtual function table of *b
b = make_base("derived");
if (b) {
b->foo();
}
return 0;
}
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