• Greg (unregistered)

    Virtual inheritance is a solution to a problem you shouldn't have in the first place. There probably are some very niche problems where this is the best solution, but in general the need for virtual inheritance is an indication your class hierarchy needs looking at with a very critical eye.

  • Hmmmm 🤨 (unregistered)

    Well, it does virtually nothing...

  • (nodebb)

    Virtual inheritance is one workaround for the "diamond problem",

    It's not, as such, a workaround, but an actual solution. And unless you can redesign your class hierarchy to eliminate the diamond (e.g. in the example given, by making one or both of B or C not inherit from A), (as far as I know) virtual inheritance is not "one workaround", but, in fact, the only solution.

    But in general virtual inheritance should be avoided almost at all costs, and multiple inheritance should be mostly avoided. In C++, it's still allowed by the language, and multiple inheritance is required to do interface-implementation split development ("implements" in Java terms). Java allows at most one "extends" inheritance, and as many "implements" inheritances as you need, but C++ has only "extends" inheritance. That means that the C++ programmer must manually construct the interface classes to be completely pure-virtual with no data members, and to have no concrete implementations of any function except the destructor, which might as well, in C++11+, be ~ClassName() = default; because it has exactly nothing to do.

  • 516052 (unregistered)

    Multiple inheritance is like a tourniquet. You newer want to use it. And if you ever do use it odds are you are not having a good day. But by all that is holy is it better to have it and not need it than need it and not have it.

  • (nodebb)

    Boy, this just sounds like another reason to avoid multiple inheritance.

  • Missing Semicolon (unregistered)

    The fact that multiple inheritance is implemented, but diamond inheritance breaks everything is surely TRWTF of C++. It should Just Work.

    Frankly C++ is TRWTF all by itself.

  • (nodebb) in reply to dpm

    Boy, this just sounds like another reason to avoid multiple inheritance.

    Or at least to do it very carefully, using only one real base class ("extends") and N (0..XXXXXX) virtual-function "interface" classes ("implements"). (In such a case, you don't have a diamond problem.)

  • (nodebb)

    I've never understood how anyone can think multiple inheritance is a good idea. I've been coding in C#, which doesn't support multiple inheritance, for 25 years. Not a single time in all those years, have I thought, "oh, this calls for multiple inheritance, too bad I can't do it". Not once. Implementing multiple interfaces, on the other hand, is often useful.

  • (nodebb) in reply to tom103

    Whence my comment about restricting use of C++'s multiple inheritance to one concrete base class and a series of pure-virtual-function "interface" classes, which corresponds more or less perfectly with one "extends" and N "implents" above (or would be if we were talking about Java or C♭). An "interface" class is a simple class in which there are no data fields, and all the functions are pure virtual with no concrete "base class" implementations at all, except for the destructor, which can be just = default;.

  • (nodebb)

    the C# programming language [...] was first widely distributed in July 2000

    I've been coding in C# [...] for 25 years

    Please change your username to "tom_Early_Adopter_103".

  • Argle (unregistered) in reply to dpm

    I've been coding in C# [...] for 25 years

    Please change your username to "tom_Early_Adopter_103".

    That would have to be me, too. I probably could have written Tom's comment verbatim. I jumped on C# because I could teach the intro to programming concepts I wanted to teach without my students having to deal with all the difficulties C++ gave them as beginners.

  • Jimbob (unregistered)

    But what if some future change to the C++ language allows the 'final' keyword to be overridden and a final class subclassed?

    Really, making every inheritance virtual is just future-proofing the code to a genius extent.

  • A Human (unregistered) in reply to Argle

    And why could you have not java used?

  • Steve (not that one) (unregistered)

    Ok, naive question: Why can't a language handle the diamond problem internally? Surely it could detect the diamond and load only one instance of A? Relying on a programmer to spot a diamond - especially since it could be buried in multiple layers (B extends E which extends A while C extends F which extends G which extends A, for example) is problematic.

  • (nodebb) in reply to tom103

    I've been coding in C#, which doesn't support multiple inheritance, for 25 years. Not a single time in all those years, have I thought, "oh, this calls for multiple inheritance, too bad I can't do it". Not once.

    I have. Which is why I'm glad that Microsoft added default interface implementations in C# 8 (in 2019). That way, there's a possibility you can implement an interface, such as a sortability interface or a comparability interface, and not have to do a boilerplate implementation for trivial scenarios. There's a lot of theory that one of the features of object oriented programming that causes the most problems is inheritance. It's been seriously discussed that OO would have been better off if it only offered interface implementation and inheritance never existed.

  • C++ for 30 Years and Counting (unregistered)

    The idea of an 'is-a' relationship and how that relationship is modeled in the context of a programming language are orthogonal concepts.

    C++ has inheritance that can be used to implement the 'is-a' relationship (templates can be too, google for CRTP), but that feature is used for many other things as well. You see multi-inheritance used all over the place in e.g. the STL headers to implement what in other languages might be considered 'mix-ins' or as a way of importing types declarations. C++ inheritance is a tool that's way more flexible (and way more foot-gun capable) than other languages inheritance features which are more strictly modeled to the OO paradigm. Maybe C++ calling it 'inheritance' was the real problem instead of something else...

    These weird coding guidelines I've seen over the years like: "Never use multi-inheritance" (which excludes the STL) , or "Every destructor must be declared virtual just in case you might inherit from the class someday", etc. are what I call "process smells". It's common when I've dug into them where the ultimate source was something like, "I read it on the internet somewhere". If someone reads similar advice here, I'd like to offer this comment as a counter.

    Developers. Before accepting a job, ask them for the coding standard. It will say lot about the company you work for

  • C++ for 30 Years and Counting (unregistered)

    An interview question I've asked over the years for C++ positions are: Q: "When do you make the destructor virtual?" A: When there exist other virtual methods in the class.

    The ones that answer successfully, usually do it along this reasoning: Using (the typically expressed via inheritance) run-time polymorphism in C++ is via base class pointer to a class that has a virtual method. If you have no virtual methods, you'll never be making polymorphic calls on a base class pointer and you'll never call 'delete' on anything other than the leaf class pointer.

    The technical reason: If there are no virtual methods, there is no vtable. Don't needlessly create one by making the destructor virtual

  • MaryD (unregistered)

    "Developers. Before accepting a job, ask them for the coding standard. It will say lot about the company you work for"

    One place I was applying I did that and the reply was: "You an run the code through the pretty printer any time you like".

  • 516052 (unregistered) in reply to Steve (not that one)

    Because the issue is not that A is loaded multiple times. The issue is that if you have A, B : A, C : A, D : B and C you can have situations where both B and C overload the same method.

    Say for example we have a class called Animal that has a method called Move(int meters). Than you have classes Fish and Bird which inherit from Animal and implement swimming and flying respectively in that method. And than you have class D, flying fish which inherits both Fish and Bird. (Silly example, I know. But it is intended to be humorous.)

    How is the language supposed to know which implementation is the correct one? Should the flying fish swim or fly? After all, both implementations are equally valid. So which to pick?

    And while there are ways around this, none of them are pretty. You can forbid multiple inheritance. But that's genuinely problematic in some situations. You can force subclasses to call the exact method explicitly. As in: ((fish)flying fish).Swim(3) but that breaks polymorphism. Etc. etc.

    ==============================================
    As for when multiple inheritance its very useful in certain niche situations. Most notably when you have what I like to call Lego block construction. That's when you have a bunch of small classes each of whom represents a certain subset of behavior and than a bunch of larger ones that grab different combinations of these. So say you have behavior classes B1, B2... B10 and object classes O1 = {B1, B3, B5}, O2 = {B1, B2, B4} etc. And this sort of problem happens far too often in some fields.

    Now ordinarily this is a textbook case for composition over inheritance. But that is limiting if you actually need to be able to override the specifics of one or the other in a specific class. Like, if say O2 and O1 need to have the same B1 behavior except for one of its 10 internal methods that needs to be O specific.

    This sounds oddly specific. But I have run into situations more than once where this is actually the optimal architecture.

  • (nodebb) in reply to 516052

    Say for example we have a class called Animal that has a method called Move(int meters).

    Yeah, but no. This is a very poor model for an actual animal, because it doesn't specify how the animal moves. Consider a Dog : public Animal - Move doesn't distinguish between walking or running. Consider a Bird : public Animal - Move doesn't distinguish between walking (pretty much all birds can walk), running (mostly ratites(1)), flying (not ratites, and not penguins either), surface swimming (ducks, geese, etc.), and deep swimming (cormorants, penguins...). Other movement modes of animals include slithering (snakes, slow-worms, ...), hopping (rabbits, kangaroos, frogs and toads, certain birds) instead of walking. Does a Horse : public Animal need support for walk/trot/canter/gallop?

    I'd approach this by observing a lack of commonality here (even walking and swimming aren't universal), which suggests that the classes that represent individual species (? in most cases, perhaps genuses ?) should derive from ("implement") interfaces (IWalk, IRun, IFly, ISurfaceSwim, IDeepSwim, IBeSessile, etc.). A class derived from Animal that only swims (specifically a fish) would implement only IDeepSwim, but a flying fish would also implement IGlide. And as if by magic, the multiple inheritance problem immediately disappears, because it has degraded to one "extends" and up to N "implements" rather than more than one "extends".

    Addendum 2026-10-15 05:58: (1) Ostriches, emus, rheas, that sort of thing. They cannot fly because they don't have a keel to attach a flying bird's "pull wings down" muscles too in the middle of the chest. (The keel is usually a big plate of cartilage, e.g. the stiff white ridge in the middle of a chicken's chest, between the two big slabs of "chicken breast".)

  • 516052 (unregistered) in reply to Steve_The_Cynic

    I know it's a poor model. But it's a nice representative example with a bit of humor. The sort of thing you'd find in a book. But the Lego block problem is real. And using composition to avoid it tends to lead to very ugly inheritance trees for the behavior classes or worse copy paste coding.

  • Steve (not that one) (unregistered) in reply to 516052

    I thought about that. I can see three main options: (1) [bad, very bad] just pick one (first one loaded, e.g.) (2) [still bad, but not as bad] same as (1) but flag a warning so the developer can disambiguate, and (3) error out with a decent error message and force the developer to disambiguate.

  • RossPresser (unregistered) in reply to Steve (not that one)

    Because automatically detecting and diagnosing a diamond with many layers during compilation is a Hard Problem.

  • 516052 (unregistered) in reply to Steve (not that one)

    If I was drafting a solution mine would be something like this:

    Allow multiple inheritance but when ever a diamond conflict happens with methods (B and C implement the same method, D inherits B and C) that is a compiler error. To fix that error the developer must include a separate override for each conflicting method in D. And than he gets to solve that problem.

    The benefit of this approach is that it puts the onus of resolving the diamond on the developer who is the only one that can actually do so. And the benefit of making it an error is that he gets no choice in the matter.

  • Steve (not that one) (unregistered) in reply to RossPresser

    I'm not convinced it's all that hard anymore with today's tech. It's really a bookkeeping issue, isn't it? Heck, I bet giving AI an Excel spreadsheet would work. (Uh, that's a joke, mostly.)

  • airdrik (unregistered) in reply to Steve (not that one)

    It's such a hard problem that Python managed to solve something like 25 years ago: https://docs.python.org/3/howto/mro.html#python-2-3-mro (well, not quite 25. it came out in python 2.3 which was released in 2003) 'basically' they have a standard way of "linearizing" the class hierarchy for a given class which contributes to deciding which version of a method gets used as well as how super references are resolved to call inherited versions of the method (such as making sure all of the inherited init methods get called during construction). And if you don't like the version you got for your class, you can always override it and call things more explicitly.
    From my experience (of dabbling in python here and there for nearly that long) ... well, I have actually implemented something that used it once or twice.

  • (nodebb) in reply to airdrik

    The difference is that Python doesn't have to deal with binding fields and methods positionally within some struct (or vtable). Instead, it binds things by looking them up by name at runtime, which is slower but handles multiple inheritance far more easily.

    Except for Python objects with slots, and there you can't have multiple inheritance.

  • o11c (unregistered) in reply to C++ for 30 Years and Counting

    That's wrong. The correct rule is: every destructor must be at least (and usually only) one of: protected, final, or virtual. This rule is needed so that it is impossible to ever delete a subclass instance through a base class destructor. As for combinations: protected and final doesn't make sense, virtual and final is expected for leaf classes whose parent already forced virtuality, and virtual and protected is dubious but not completely unreasonable.

    If the destructor is not public (note that private doesn't make sense), it is impossible to call the dtor through a base class pointer in the first place.

    If the destructor is final (really: the class is final), the direct call will always be the correct dtor. This is probably what your rule is intending but it is not enforcing.

    Otherwise, the destructor must be virtual, even if you don't have any virtual methods, just in case somebody does end up subclassing you and adding more fields (which is not always a nonsensical thing to do - for example, a base class that takes a caller-provided buffer might have a subclass that sets the base class fields to something stored in the derived class, even without any virtual methods being involved).

    Note that if using shared pointers, you can destroy a pointer-to-base without accessing the base's dtor, as long as you first construct the fat pointer as a pointer-to-derived, so you can use protected in this case .

Leave a comment on “A Diamond in the Rough”

Log In or post as a guest

Replying to comment #705378:

« Return to Article