5

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?

AlwaysLearning
  • 7,257
  • 4
  • 33
  • 68
  • Do you mean "accessing members of a most-derived class given only a pointer to the virtual base subobject"? – Kerrek SB Dec 13 '15 at 20:43
  • Every lookup to the virtual table has a non-zero price. The answer depends on implementation. I'm guessing that it's an O(1) hash table. – duffymo Dec 13 '15 at 20:45
  • No virtual members. The members are overridden. Virtual inheritance is used to allow multiple paths to the same base. – AlwaysLearning Dec 13 '15 at 20:46
  • Related to [what-is-the-vtt-for-a-class](http://stackoverflow.com/questions/6258559/what-is-the-vtt-for-a-class) – Jarod42 Dec 13 '15 at 21:12
  • 1
    If there are no virtual functions there is nothing to override. – Pete Becker Dec 13 '15 at 21:45
  • @PeteBecker Do you mean that the compiler will be able to prove this and guarantee zero time overhead? – AlwaysLearning Dec 13 '15 at 22:24
  • 1
    No. Overriding is about virtual functions. If there are no virtual functions there is nothing to override. – Pete Becker Dec 13 '15 at 22:40
  • @PeteBecker Would `hide` be the correct term instead of `override`? – AlwaysLearning Dec 15 '15 at 08:03
  • It depends on what you're trying to say. If you have a non-virtual function in a base class and a function in a derived class with the same signature and you call that signature through the derived class you get the derived version; if you call it through a pointer or reference to the base class you get the base version. So when you try to use it, in many cases it's hidden. But that's from the perspective of an outsider trying to call the function; overriding is about the internals of the class hierarchy, so is in a somewhat different context. – Pete Becker Dec 15 '15 at 14:48
  • (continued) While it's quite common for a class designer to say "this function is intended to override the base version", it would be quite rare to say "this function is intended to hide the base version". – Pete Becker Dec 15 '15 at 14:52
  • @PeteBecker But what is the term that's applicable to a function/data member in the derived class with the same name as in the base class? In any case, I erased the item. – AlwaysLearning Dec 15 '15 at 20:39
  • How about "a mistake"? – Pete Becker Dec 15 '15 at 20:40
  • @PeteBecker Is it necessarily a mistake? If it was so, there would not be a word `virtual` in the language -- members would always be virtual. – AlwaysLearning Dec 16 '15 at 08:33

2 Answers2

5

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;
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
3

Access to the data members of the virtual base class is via an extra indirection.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Aha. This means that accesses to new data members introduced by the derived classes would not go through the virtual table. Is this correct? – AlwaysLearning Dec 13 '15 at 22:03
  • @AlwaysLearning Only derived to virtual base conversions need to use the vtable (or a pointer in MSVC++). – curiousguy Dec 15 '15 at 03:16