-2

If we don't care about performance, seems virtual inheritance can replace normal inheritance at any time. Is there any real situation that we can only use normal inheritance? In my opinion, when we use inheritance, we always want the derived class can convert to base class. But with normal inheritance, we still make the derived class's data is composited by base class's data. Even though such composition makes we can convert the derived class to base class, but that's not really requirement.

macomphy
  • 413
  • 1
  • 9
  • 1
    Inheritance isn't necessary as well. It is convenient for the model you are creating. – Captain Giraffe Jun 05 '22 at 16:01
  • 2
    Every most-derived class must explicitly construct all virtual base classes. That's inconvenient at best; not something you want to do for every class, but only when truly necessary. – Igor Tandetnik Jun 05 '22 at 16:03
  • If you need multiple copies of a base class you can’t make it virtual. One example is a type that can be held in two intrusive lists. – Pete Becker Jun 05 '22 at 16:05
  • 3
    "*If we don't care about performance*" If performance is so irrelevant, why are you bothering with C++? Seriously, if performance doesn't matter to your needs, just use Python. You'll be so much more productive. – Nicol Bolas Jun 05 '22 at 16:19
  • 2
    Don't need virtual inheritance if you don't have multiple inheritance. – Eljay Jun 05 '22 at 16:22
  • In fact, there are many skill to do sometimes like virtual inheritance without performance cost. @NicolBolas – macomphy Jun 05 '22 at 16:22
  • @macomphy: If you're using runtime polymorphism (aka: the only reason to want `virtual` *anything*), then you will pay the virtual base class penalty every time you call a virtual function though the virtual base class. So "without performance cost" is not a thing. – Nicol Bolas Jun 05 '22 at 16:25
  • You can use crtp to simulate virtual inheritance and use fat pointer to simulate runtime polymorphism. @NicolBolas – macomphy Jun 05 '22 at 16:30
  • 1
    @macomphy: But then you're not using virtual inheritance, so your question is irrelevant. – Nicol Bolas Jun 05 '22 at 16:31
  • 1
    If you don't care about performance, you might as well use some other language. – Aykhan Hagverdili Jun 05 '22 at 16:34
  • C++ has a lot of advantages besides performance (e.g. very good abstraction features). And if the inherited classes are not called millions of times, but in high-level code you can optimize the performance in the code, where it matters instead. Other language comparison comments are off-topic to the question here. – Sebastian Jun 05 '22 at 17:15
  • If you want to know the actual type at compile-time, inheritance would be a bad solution at all. With virtual inheritance the cases, where your base classes need distinct data would not work any longer. – Sebastian Jun 05 '22 at 17:22
  • *"we still make the derived class's data is composited by base class's data."* -- huh? -- *"Even though such composition makes we can convert the derived class to base class,"* -- what is this sentence structure? I cannot parse an intended meaning from it. – JaMiT Jun 05 '22 at 18:07

2 Answers2

1

Aside from performance issues (which will be paid on every virtual function call through a virtual base class), there are two major problems with making everything a virtual base class.

First, each derived class must initialize all of the virtual base classes that it uses. All of them, all the way up the hierarchy. This is a huge hassle; every time you write a new derived class or even just a new constructor for the derived class, you now may have to forward a bunch of parameters manually. Worse, if a type in the middle of a hierarchy needs a new base class, every derived class has to know about it and possibly call particular constructors of it.

By contrast, with non-virtual inheritance, each class in the hierarchy is responsible for initializing its bases without making derived classes even know those base classes exist. This makes non-virtual inheritance a lot easier to use and a lot more composition-friendly. And maintainable.

Second, if you do this all the time, you can get cases where diamond inheritance is not what you wanted. Just because two classes happen to inherit from the same base class does not mean that a class derived from both wants to combine that base class into a single instance. Indeed, one of the reasons why virtual inheritance is not the default is because such circumstances are actually quite rare.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thanks for reply, but at current, we nearly cannot see multiple inheritance in c++ code. So, does the normal behavior really right? – macomphy Jun 05 '22 at 16:43
  • @macomphy: I don't really understand what you mean. Virtual inheritance is rare in C++ code because the problem virtual inheritance is meant to solve is rare. It's a specific tool for a particular use case. Trying to use it everywhere by default is like trying to use a scalpel as a butcher knife. – Nicol Bolas Jun 05 '22 at 16:49
  • I think when we need multiple inheritance, we need virtual inheritance and don't use virtual inheritance as default behavior makes we rarely use multiple inheritance. – macomphy Jun 05 '22 at 16:55
  • @macomphy Do you have statistics from code, where the default would not prevent the expert programmers to use the suitable solution, e.g. llvm source itself? How often is virtual inheritance used? – Sebastian Jun 05 '22 at 17:28
  • @Sebastian , in stl, iostream use multiple inheritance and it use virtual inheritance. In QT, many class is derived from `QObject` and they cannot use multiple inheritance. – macomphy Jun 05 '22 at 17:33
  • @macomphy They cannot use multiple inheritance at all or not virtual or only virtual? Is one word so critical to type? – Sebastian Jun 05 '22 at 17:39
  • @Sebastian here is a relating question. https://stackoverflow.com/questions/3201273/what-is-the-correct-way-of-multiple-inheritance-in-qt-c – macomphy Jun 05 '22 at 17:45
  • @macomphy: "*I think when we need multiple inheritance, we need virtual inheritance*" That's incorrect. IOStreams uses virtual inheritance because of the *way* in which it uses multiple inheritance. But if I wanted to make an object inherit from both `std::ostream` and `QObject`, that's something that doesn't need virtual inheritance for either of those base classes. It would be 100% OK to do so without virtual inheritance. – Nicol Bolas Jun 05 '22 at 17:45
  • If such class will never be inherited by others, you are right. But it may be inherited by other class and cause diamond inheritance finally. – macomphy Jun 05 '22 at 17:49
  • @macomphy Those class relationships mentioned in the other question could be rewritten to only inherit once from `QObject`, e.g. with templates: `IRzPlugin` inheriting from `QWidget` (if you need a common base class with `IRzPlugin` both can derive from one) or by composition – Sebastian Jun 05 '22 at 17:59
  • Yes, I think it is a good idea. And that's exactly my opinion: most inheritance should be virtual inheritance in design side, but you can use some skill like crtp to simulate virtual inheritance. @Sebastian – macomphy Jun 05 '22 at 18:02
  • 1
    The Qt developers call virtual multiple inheritance a sign for bad design: https://forum.qt.io/topic/88295/with-qt-5-6-is-it-possible-to-avoid-the-diamond-problem-when-signals-and-slots-are-defined-in-two-branches/2 Even if you do not follow that opinion, that could be the reason it is not well supported with Qt classes. – Sebastian Jun 05 '22 at 18:02
  • @macomphy: "*But it may be inherited by other class and cause diamond inheritance finally.*" But do you actually *want* "diamond inheritance"? See, just because the same base class exists twice in the same hierarchy does not mean the type needs "diamond inheritance". This is what the last paragraph of my post is about. – Nicol Bolas Jun 05 '22 at 18:18
  • This is just the beginning, you cannot ensure we don't need diamond inheritance at all time. We can use multiple inheritance at begin time. But with time passed, we need diamond inheritance at some time and it's hard to change normal inheritance to virtual inheritance. Then you have to use a ugly way to write later code. So many repository forbid multiple inheritance, and that's why I say no virtual inheritance, no multiple inheritance. – macomphy Jun 05 '22 at 18:41
  • There are a lot of times, virtual multiple inheritance would not work. – Sebastian Jun 05 '22 at 18:50
  • Only few mainstream languages (Perl, Raku, Tcl, Object Rexx, Python, C++ and some functional languages with OOP extensions) implemented multiple inheritance at all. And virtual multiple inheritance is the more troublesome. – Sebastian Jun 05 '22 at 18:54
  • @macomphy: "*you cannot ensure we don't need diamond inheritance at all time.*" True. That's why virtual inheritance exists. What you *can* say is that users of a particular type are *not allowed* to apply diamond inheritance to my type's base classes, because that's not how my type is intended to work. – Nicol Bolas Jun 05 '22 at 23:36
  • @Sebastian Java says it don't support multiple inheritance, but it support multiple interface inheritance with default implementation, which is very similar with multiple inheritance. C# is same as Java. As you can see, the most popular languages (python, C++, C# and Java) all support multiple inheritance. (of course, c don't support multiple inheritance.) – macomphy Jun 06 '22 at 01:27
  • 1
    @macomphy: "*which is very similar with multiple inheritance*" But it's not the same as real multiple inheritance. Indeed, the difference is what makes *all* the difference. At the language level, Java recognizes the concept of an interface which a class can implement, as distinct from the concept of a base class which is a thing a class *is*. The former are types where C++ virtual inheritance makes sense, and the latter are types for which it does not. – Nicol Bolas Jun 06 '22 at 01:37
  • @macomphy: Also, by ensuring that all of Java's "virtual base classes" only consist of pure virtual functions, Java is able to avoid the pain of *implementing* virtual base classes. Interfaces have no constructors; they're just a bundle of virtual functions, so Java doesn't have to deal with the question of how to initialize them. Etc. The restrictions on Java interfaces were deliberately made to *avoid* the C++ multiple inheritance problems. – Nicol Bolas Jun 06 '22 at 01:41
  • Java has same problem too. https://www.tutorialspoint.com/what-is-diamond-problem-in-case-of-multiple-inheritance-in-java – macomphy Jun 06 '22 at 01:45
  • @macomphy: That's not remotely the "same problem", since it doesn't involve inheriting from the same interface through different base classes. It's merely two functions that happen to have the same name and signature. Also, that required a language change in version 8, which allowed interfaces to have default methods. And even then, all that means is that you have to explicitly specify which default implementation you're calling when you call the "superclass" version. – Nicol Bolas Jun 06 '22 at 01:58
  • @macomphy The same linked article says after describing the diamond problem *Due to this Java does not support multiple inheritance* – Sebastian Jun 06 '22 at 05:12
  • Two member functions of different classes having the same signature can be used with C++ templates; to additionally make sure it is used only with the intended classes there is SFINAE and there are concepts now. What would be lacking is making all member functions used in this way virtual. – Sebastian Jun 06 '22 at 05:15
  • Another question would be: What would be the easiest way to use default functions like in Java with C++. – Sebastian Jun 06 '22 at 05:20
  • Here is described how Java default methods work: https://stackoverflow.com/questions/52620936/why-does-java-not-allow-multiple-inheritance-but-does-allow-conforming-to-multip/52621193#52621193 Basically it is like non-virtual (!) multiple inheritance in C++ without fields. – Sebastian Jun 06 '22 at 05:28
  • @Sebastian Well, I am not familiar with Java. But when I try to simulate Java's multiple interface inheritance in C++. Here is the example: https://godbolt.org/z/nreYvrT4n . As you can see, I have to use virtual inheritance to simulate Java's multiple interface inheritance, so, we indeed need virtual inheritance in multiple inheritance. – macomphy Jun 06 '22 at 06:54
  • Function is data, data is function. Multiple interface inheritance has no difference with multiple inheritance. – macomphy Jun 06 '22 at 07:03
  • 1
    Perhaps you need virtual inheritance for implementing in C++. A few more interesting ones: You do not need the virtual inheritance all the way up the chain, only at the lowest level: https://godbolt.org/z/9vMhfdhe3 . If you do not provide an implementation in C, you get no output: https://godbolt.org/z/nr7roPnYx Even not, if there is single inheritance https://godbolt.org/z/Y15zqhM3f Without virtual you get a warning for multiple inheritance https://godbolt.org/z/EPdYnqGxW which you can resolve https://godbolt.org/z/bxn6Mo51h or an output for single inheritance: https://godbolt.org/z/WG3a4ahnb – Sebastian Jun 06 '22 at 07:06
  • So there are uses for both, virtual and non-virtual multiple inheritance, as you lose the implementation of the ancestors with virtual inheritance. – Sebastian Jun 06 '22 at 07:08
  • Even if you specify the base class implementing the function, you get no output with virtual inheritance: https://godbolt.org/z/vvGjqvjve – Sebastian Jun 06 '22 at 07:11
  • @Sebastian , wow, you are right, I didn't notice that. But we only have one interface in every derived class. The unique is more important and is guaranteed by virtual inheritance. – macomphy Jun 06 '22 at 07:17
  • If `interface` has no default constructor, the derived classes (even further down the chain) get an error, if they do not call the non-default one (which can be helpful either for avoiding errors or for understanding). You can mix virtual and non-virtual multiple inheritance https://godbolt.org/z/TWWf8sben - sorry for the q'n'd UB output of the contents of `c` - some interfaces from the ancestors are put together, some are still separate. So you end up with two incarnations, which is easier to see in the object representation with one data type. – Sebastian Jun 06 '22 at 07:41
  • We call `foo()` from `D` and `E` and the `D::foo()`, which is down the chain `virtual` uses the implementation from the most upper one, that is directly out of `C`. `E::foo`, which is down the chain non-`virtual` uses the highest one below `E`, which is the one from `B`. So with `virtual` it goes all the way up, with non-`virtual` it goes down the inheritance hierarchy. – Sebastian Jun 06 '22 at 07:51
  • Or take this https://godbolt.org/z/c1Pfa9qoq You have three `interface` classes stored in `C`, one directly and two inside a `B`, one of the two `B` inside a `E`. In Detail: You have the `virtual` `interface` class (over `A`->`D` as well as over `A2`; and actually (`virtual`) `interface` is stored in the end only once directly in `C`), the non-`virtual` `interface` inside non-`virtual` `B` (over `E`) and the non-`virtual` `interface` inside `virtual` `B` (over `E2` and actually (`virtual`) `B` is stored in the end only directly in `C`). – Sebastian Jun 06 '22 at 08:11
0

I know what to do now. Think about a classic diamond inheritance example.

B inherited from A. C inherited from A. D inherited from B and C.

A is composed by impA. B is composed by impA and something extension in B, I call it as impB. C is composed by impA and something extension in C, I call it as impC.

D should be composed by impA, impB, impC and impD. But with normal inheritance, D is composed by twice impA, once impB, once impC and impD. impA appeared twice and that's the diamond problem. To solve this, we can use virtual inheritance.

For virtual inheritance is counterintuitive (just like people in this question), there is a better way to solve such problem. We can just write impA, impB, impC and impD. Then make A inherited from impA. B inherited from impA and impB. C inherited from impA and impC. D inherited from impA, impB, impC and impD. Then problem is solved.

struct impA {
    void a() {}
};

template <typename derived>
struct impB {
    void b() {
        auto&& d = static_cast<derived&>(*this);
        d.a();
        d.a();
    }
};

template <typename derived>
struct impC {
    void c() {
        auto&& d = static_cast<derived&>(*this);
        d.a();
        d.a();
    }
};


template <typename derived>
struct impD {
    void d() {
        auto&& d = static_cast<derived&>(*this);
        d.a();
        d.b();
        d.c();
    }
};

struct A : public impA {};

struct B : public impA, impB<B> {};

struct C : public impA, impC<C> {};

struct D : public impA, impB<D>, impC<D>, impD<D> {};

int main() {
    A{}.a();

    B{}.a();
    B{}.b();

    C{}.a();
    C{}.c();

    D{}.a();
    D{}.b();
    D{}.c();
    D{}.d();
}
macomphy
  • 413
  • 1
  • 9