8

I read a talk entitled "Deep C (and C++)" by Olve Maudal and Jon Jagger. I paste the relevant code and quote below, but here is the link to the slides.

On slide 349, the presenters show this C++ code snippet which deliberately contains issues:

#include "B.hpp"
class A {
public:
     A(int sz) { sz_ = sz; v = new B[sz_]; } 
     virtual ~A() { delete[] v; }
     // ...
private:
      A(const A &);
      A & operator=(const A &); // ...
      B * v;
      int sz_;
};

The picture shows a quote from their "expert programmer" character about this code snippet, which says:

What is the point of having a virtual destructor on a class like this? There are no virtual functions so it does not make sense to inherit from it. I know that there are programmers who do inherit from non-virtual classes, but I suspect they have misunderstood a key concept of object orientation. I suggest you remove the virtual specifier from the destructor, it indicates that the class is designed to be used as a base class - while it obviously is not.

Emphasis mine.

I have never heard this before - I inherit from non-abstract classes e.g. in order to add methods to code from a library. What key concept of OOP am I breaking by doing this, or have I misunderstood?

5
  • 1
    It's not bad practice per se. It's just a bit tangential to OOP. OOP is about a derived class being able to respond to a message (method call) that a superclass is able to handle. In strongly typed languages, this requires inheritance and virtual methods, but generally speaking, inheritance is not required (e.g. consider JavaScript). But in C++ (and many mainstream OO languages), inheritance does a double duty as a reuse mechanism (so, may be used for relationships other than "is-a"). Also C++ has things like private inheritance, which is just hardcoded composition, when you think about it. Dec 15, 2022 at 6:22
  • 2
    "abstract class" and "class with virtual methods" are not quite the same thing.
    – pjc50
    Dec 15, 2022 at 11:59
  • When extending these classes, are you adding additional properties/state?
    – JimmyJames
    Dec 15, 2022 at 16:07
  • @JimmyJames Usually yes, but not always. Why do you ask?
    – Eoin
    Dec 18, 2022 at 16:46
  • @Eoin When you are only adding methods, I don't think much that can go wrong aside from manipulating state in a way that the base class is not designed to handle, I suppose. In my experience, when you add additional state to the subclass, that's where things can get dicey and even completely unworkable. This is especially true if those properties become part of an object's logical identity. The Shape/ColoredShape problem is a good example of this.
    – JimmyJames
    Dec 19, 2022 at 16:59

4 Answers 4

4

The problem is that inheriting from a class with no virtual methods, will cause you problems if you inherit from it and overwrite any of the methods, and if you do not overwrite any of the methods why then would you inherit from it? The problem being that in C++ methods that are not marked virtual will not allow you to utilize the polymorphism that is fundamental to OO. Only virtual methods have a vtable, which will allow the base class to call the overwritten method.

If a class is not made abstract, or does not have a virtual destructor it is not prepared for being inherited from, and it is fair to assume that the author of the class does not intend you to inherit from it. When developing in Java I often state that classes should be either abstract or final, just to enforce this. In C++ the final keyword were introduced later, but I believe it should be used more often for the same reason.

You mention that you inherit from classes to add methods to them, and if I understand you correctly you add methods that the base class does not have, so you do not overwrite methods, but merely add new methods. By doing that, you are creating a class with a new functionality that is tightly coupled to the class you are extending. A better approach, in that case, would be to use Composition over Inheritance.

So in brief, the concept you might be violating is polymorphism. And to answer the title question: Yes, inheriting from a non-abstract class, is most likely a wrong design decision.

6
  • 1
    Thanks for your answer :) I don't inherit from library code often - but sometimes it is far easier if we don't care much about coupling in certain circumstances. By marking everything final don't you force people using your code to break the open-closed principle? I feel like there are some disagreements in the OOP community about these concepts.
    – Eoin
    Dec 14, 2022 at 9:52
  • 1
    I do not believe that force them to violate the open-closed principle. The part about being open for extension is not limited to inheritance, and in most cases it is more beneficial to favor composition instead, as also stated in the "Design Patterns" book. Often the code that is the easiest to write can be the most difficult to maintain, especially in C++, but that is just my take on it at least. Dec 14, 2022 at 9:54
  • 2
    Worth to say that we are not forced to satisfy all the SOLID principles just because happens we make solutions OOP. The open-close principle applies only if it applies. No one forces you to make your code extensible. It's extensible only when you need it to be... OOP or not.
    – Laiv
    Dec 14, 2022 at 10:26
  • 1
    @VioletGiraffe I might not have explained well if you read from it that inheritance can ONLY be used for polymorphism. That is not my intended message to convey. However my point is, that extending library classes to add new functions to them, is a way of creating a high coupling, which is generally frowned upon. In that case I would favor composition instead. Which is also something the standard library does a lot. Dec 15, 2022 at 8:55
  • 2
    @TommyAndersen: on that you are completely right and I agree, thanks for clarifying. But sometimes hard coupling is OK, in small implementation details rather than in the system-level architecture. Dec 15, 2022 at 9:24
2

Strictly speaking, by just inheriting from a class to add some functionality you do not break any key concepts of OOP. Inheriting for extension is arguably a valid theoretical use of inheritance.

It's just not much preferred in practice in comparison to, say, creating a wrapper object, because you and your colleagues will need extra discipline to not modify any existing parent behavior. If you do accidentally modify it, as Tommy's answer correctly points out, in C++ you may just end up with an incorrectly working program. In a language like Java the polymorphism will work, but it's still rich soil for unexpected breakages if the class wasn't designed for inheritance.

That said, there are cases when non-abstract classes are designed for inheritance. In C++ it's simple: such class will declare at least one virtual method (it may still be non-abstract, see here). That's what the presenters are talking about. In Java, and many other languages, you often have to rely on library developers to be disciplined and properly annotate/document their types, and your own understanding. Often, not everything that should be final or abstract can be marked as such for technical and other reasons.

1
  • 2
    In Swift/Objective-C you can’t have unimplemented virtual functions. You can have a method that just calls a function Unimplemented or something like that which will crash at runtime. (And I think the compiler is clever enough to figure out that calling it never returns, so an “unimplemented” method that is declared with a return value doesn’t need to have a return statement).
    – gnasher729
    Dec 14, 2022 at 21:19
2

In C++, functions are non-virtual by default. They need to marked explicitly as virtual in order to allow being overridden.

Contrast this with languages like C# or Java, where every function is virtual unless explicitly made non-virtual by declaring it as final.

In C# and Java (and other languages, of course), you can inherit from any non-final class and override any non-final methods, which there are plenty of.

In C++, this class as given makes little sense. The destructor is marked virtual, indicating that in principle, this class is designed for subclassing. However, there is nothing useful you can do via subclassing, as it has no non-virtual method for you to override.

Subclassing is done to use an instance of the subclass in place of the super class. However, if you cannot override any method, you can never change the behavior of the class.

And if you subclass to add new methods, then no-one who expects a superclass will ever call those methods. Only consumers that explicitly expect the subclass can call those methods. However, in those cases, you could just use composition over inheritance and probably get a better design.

Polymorphism is only useful if the class has some public API that it is polymorphic in. This base class doesn't have that.

3
  • “Override” and “virtual” are not the same. You can have a different function with the same name and arguments in a subclass - that’s an override, and the compiler chooses which function to call at compile time. And you can have many virtual implementations of one function, and the right implementation is picked at runtime.
    – gnasher729
    Dec 17, 2022 at 18:20
  • @gnasher729 But that is more the difference between static and dynamic dispatch, isn't it? Languages that do not do static dispatch (like Java or C#) don't allow same methods with same arguments in a subclass without it being an override (well they do if the method was private, but then it is shadowing, not overriding). What you describe wouldn't count as overriding in my book -- its just making use of static dispatch. C++11 even added the override keyword so you can make sure you actually override a virtual method.
    – Polygnome
    Dec 19, 2022 at 14:22
  • The override keyword was introduced specifically to avoid the accidental non-override of methods.
    – Polygnome
    Dec 19, 2022 at 14:28
1

An interesting example is UIView in iOS development. Basically everything you see on the screen is a UIView or a subclass of it.

The class is not abstract, because it is very useful on its own - for organising sub views, and for containing callbacks. They are configurable to some degree so that different UIViews don’t require subclassing.

On the other hand, there are things that can only be done by subclassing. One is any drawing that cannot be handled by adding sub views. And there are some stupid ones where you have a method that needs to return true or false (and therefore needs subclassing to change) instead of having a property.

So in this case you subclass or not, depending on what you need.

(Now Objective-C and Swift which are mostly used on iOS don’t have “abstract classes” where creating an instance isn’t possible, but of course you can have a class that cannot usefully be instantiated. )

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.