Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Hands-On Design Patterns with C++

You're reading from   Hands-On Design Patterns with C++ Solve common C++ problems with modern design patterns and build robust applications

Arrow left icon
Product type Paperback
Published in Jul 2023
Publisher Packt
ISBN-13 9781804611555
Length 626 pages
Edition 2nd Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Fedor G. Pikus Fedor G. Pikus
Author Profile Icon Fedor G. Pikus
Fedor G. Pikus
Arrow right icon
View More author details
Toc

Table of Contents (26) Chapters Close

Preface 1. Part 1: Getting Started with C++ Features and Concepts
2. Chapter 1: An Introduction to Inheritance and Polymorphism FREE CHAPTER 3. Chapter 2: Class and Function Templates 4. Chapter 3: Memory and Ownership 5. Part 2: Common C++ Idioms
6. Chapter 4: Swap – from Simple to Subtle 7. Chapter 5: A Comprehensive Look at RAII 8. Chapter 6: Understanding Type Erasure 9. Chapter 7: SFINAE, Concepts, and Overload Resolution Management 10. Part 3: C++ Design Patterns
11. Chapter 8: The Curiously Recurring Template Pattern 12. Chapter 9: Named Arguments, Method Chaining, and the Builder Pattern 13. Chapter 10: Local Buffer Optimization 14. Chapter 11: ScopeGuard 15. Chapter 12: Friend Factory 16. Chapter 13: Virtual Constructors and Factories 17. Chapter 14: The Template Method Pattern and the Non-Virtual Idiom 18. Part 4: Advanced C++ Design Patterns
19. Chapter 15: Policy-Based Design 20. Chapter 16: Adapters and Decorators 21. Chapter 17: The Visitor Pattern and Multiple Dispatch 22. Chapter 18: Patterns for Concurrency 23. Assessments 24. Index 25. Other Books You May Enjoy

Polymorphism and virtual functions

When we discussed public inheritance earlier, we mentioned that a derived object can be used in any place where a base object is expected. Even with this requirement, it’s often useful to know what the actual type of the object is—in other words, what type the object was created as:

Derived d;
Base& b = d;
...
b.some_method(); // b is really a Derived object

some_method() is a part of the public interface of the Base class and has to be valid for the Derived class as well. But, within the flexibility allowed by the contract of the base class interface, it can do something different. As an example, we’ve already used the avian hierarchy before to represent different birds, in particular, birds that can fly. The FlyingBird class can be assumed to have a fly() method, and every specific bird class derived from it has to support flight. But eagles fly differently from vultures, and so the implementation of the fly() method in the two derived classes, Eagle and Vulture, can be different. Any code that operates on arbitrary FlyingBird objects can call the fly() method, but the results will vary depending on the actual type of the object.

This functionality is implemented in C++ using virtual functions. A virtual public function must be declared in the base class:

class FlyingBird : public Bird {
  public:
  virtual void fly(double speed, double direction) {
    ... move the bird at the specified speed
        in the given direction ...
  }
  ...
};

A derived class inherits both the declaration and the implementation of this function. The declaration and the contract it provides must be respected. If the implementation meets the needs of the derived class, there’s no need to do anything more. But if the derived class needs to change the implementation, it can override the implementation of the base class:

class Vulture : public FlyingBird {
  public:
  virtual void fly(double speed, double direction) {
    ... move the bird but accumulate
        exhaustion if too fast ...
  }
};

Note that the keyword virtual, when used in a derived class for methods that override base class virtual functions, is entirely optional and has no effect; we will see later that there are good reasons to omit that.

When a virtual function is called, the C++ runtime system must determine what the real type of the object is. Usually, this information isn’t known at compile time and must be determined at runtime:

void hunt(FlyingBird& b) {
  b.fly(...);    // Could be Vulture or Eagle
  ...
};
Eagle e;
hunt(e);   // Now b in hunt() is Eagle
           // FlyingBird::fly() is called
Vulture v;
hunt(v);   // Now b in hunt() is Vulture
           // Vulture::fly() is called

The programming technique where some code operates on any number of base objects and invokes the same methods, but the results depend on the actual type of these objects, is known as runtime polymorphism, and the objects that support this technique are polymorphic. In C++, polymorphic objects must have at least one virtual function, and only the parts of their interface that use virtual functions for some or all of the implementation are polymorphic.

It should be evident from this explanation that the declaration of the virtual function and its overrides should be identical—the programmer calls the function on a base object, but the version that’s implemented in the derived class runs instead. This can happen only if the two functions have identical arguments and return types. One exception is that if a virtual function in the base class returns a pointer or a reference to an object of some type, the override can return a pointer or a reference to an object derived from that type (this is known as covariant return types).

A very common special case of polymorphic hierarchies is one where the base class doesn’t have a good default implementation of the virtual function. For example, all flying birds fly, but they all fly at different speeds, so there’s no reason to select one speed as the default. In C++, we can refuse to provide any implementation for a virtual function in the base class.

Such functions are called pure virtual, and any base class that contains a pure virtual function is known as an abstract class:

class FlyingBird {
  public:
  virtual void fly(...) = 0;     // Pure virtual function
};

An abstract class defines an interface only; it’s the job of the concrete derived classes to implement it. If the base class contains a pure virtual function, every derived class that’s instantiated in the program must provide an implementation. In other words, an object of a base class can’t be created (a derived class could also be an abstract class, but then it cannot be instantiated directly either, we must derive another class from it). We can, however, have a pointer or a reference to an object of a base class—they really point to a derived class, but we can operate on it through the base class interface.

A few notes on the C++ syntax—when overriding a virtual function, it isn’t required to repeat the virtual keyword. If the base class declares a virtual function with the same name and arguments, the one in the derived class will always be a virtual function and will override the one from the base class. Note that, if the arguments differ, the derived class function doesn’t override anything and instead shadows the name of the base class function. This can lead to subtle bugs where the programmer intended to override a base class function but didn’t copy the declaration correctly:

class Eagle : public FlyingBird {
  public:
  void fly(int speed, double direction);
};

Here, the types of the arguments are slightly different. The Eagle::fly() function is also virtual, but it doesn’t override FlyingBird::fly(). If the latter is a pure virtual function, the bug will be caught because every pure virtual function must be implemented in a derived class. But if FlyingBird::fly() has the default implementation, then the bug will go undetected by the compiler. C++11 provides a very useful feature that greatly simplifies finding such bugs—any function that’s intended to be an override of a base class virtual function can be declared with the override keyword:

class Eagle : public FlyingBird {
  public:
  void fly(int speed, double direction) override;
};

The virtual keyword is still optional, but if the FlyingBird class doesn’t have a virtual function that we could be overriding with this declaration, this code won’t compile.

It is also possible to prevent the derived classes from overriding a virtual function by declaring it final:

class Eagle : public FlyingBird {
  public:
  // All Eagles fly the same way, derived classes BaldEagle
  // and GoldenEagle cannot change this.
  void fly(int speed, double direction) final;
};

Note that the use of the final keyword is rare: it is unusual for the design to require that from this point on, the customizations should be disabled in the hierarchy. The final keyword can also be applied to the entire class: it means that no more classes can be derived from this one. Again, this is a rare situation.

So, should or shouldn’t you use the virtual keyword on overrides? This is a matter of style, but the style affects the readability and maintainability of the code. The following is the recommended practice:

  • Any virtual function that does not override one in the base class must use the virtual keyword. This includes both the functions in classes that have no bases and the functions added in derived classes.
  • Any other virtual function should not use the virtual keyword. All overrides should use the override keyword, with the following exception, which is also another rule.
  • A final override must use the final keyword and should not use the override keyword.

There are two advantages to this approach. The first is clarity and readability: if you see virtual, this is a virtual function that does not override anything. If you see override, this must be an override (otherwise the code would not compile). If you see final, this is also an override (again, the code would not compile otherwise) and it’s the last such in the hierarchy. The second advantage shows up during code maintenance. One of the greatest problems with maintaining hierarchies is the base class fragility: you write a set of base and derived classes, someone else comes along and adds an argument to the base class function, and suddenly all your derived class functions don’t override the base class ones and never get called. With consistent use of the override keyword, this will not happen.

The most common use of virtual functions, by far, is in hierarchies that use public inheritance—since every derived object is also a base object (is-a relationship), a program can often operate on a collection of derived objects as if they were all of the same types, and the virtual function overrides ensure that the right processing happens for every object:

void MakeLoudBoom(std::vector<FlyingBird*> birds)
  for (auto bird : birds) {
    bird->fly(...);   // Same action, different results
  }
}

But virtual functions can also be used with private inheritance. The use is less straightforward (and much less common)—after all, an object that’s derived privately can’t be accessed through a base class pointer (a private base class is referred to as an inaccessible base, and an attempt to cast a derived class pointer to the base class will fail). However, there’s one context in which this cast is permitted, and that’s within a member function of the derived class. Here’s, then, the way to arrange a virtual function call from a privately inherited base class to the derived one:

class Base {
  public:
  virtual void f() {
      std::cout << "Base::f()" << std::endl;
    }
  void g() { f(); }
};
class Derived : private Base {
  public:
  virtual void f() {
    std::cout << "Derived::f()" << std::endl;
  }
  void h() { g(); }
};
Derived d;
d.h(); // Prints "Derived::f()"

The public methods of the Base class become private in the Derived class, so we can’t call them directly. We can, however, call them from another method of the Derived class, such as the public method h(). We can then call f() directly from h(), but that doesn’t prove anything—it would come as no surprise if Derived::h() invoked Derived::f().

Instead, we call the Base::g() function that’s inherited from the Base class. Inside that function, we’re in the Base class—the body of this function may have been written and compiled long before the Derived class was implemented. And yet, in this context, the virtual function override works correctly and Derived::f() is called, just as it would if the inheritance were public.

In the previous section, we recommended that composition is preferred to private inheritance unless there’s a reason to do otherwise. There’s no good way to implement similar functionality using composition; so, if the virtual function behavior is desired, private inheritance is the only way to go.

A class with a virtual method has to have its type encoded into every object—this is the only way to know, at runtime, what was the type of the object when it was constructed, after we converted the pointer into a base class pointer and lost any other information about the original type. That type information isn’t free; it takes space—a polymorphic object is always larger than an object with the same data members but no virtual methods (usually by the size of a pointer).

The extra size doesn’t depend on how many virtual functions the class has—at long as it has one, the type information must be encoded in the object. Now, recall that a pointer to the base class can be converted into a pointer to the derived class, but only if we know the correct type of the derived class. With the static cast, there’s no way to test whether our knowledge is correct. For non-polymorphic classes (classes without any virtual functions), there can be no better way; once their original type is lost, there is no way to recover it. But for polymorphic objects, the type is encoded in the object, so there has to be a way to use that information to check whether our assumption is correct about which derived object this really is. Indeed, there is a way. It’s provided by the dynamic cast:

class Base { ... };
class Derived : public Base { ... };
Base* b1 = new Derived;     // Really Derived
Base* b2 = new Base;   // Not Derived
Derived* d1 = dynamic_cast<Derived*>(b1);  // Succeeds
Derived* d2 = dynamic_cast<Derived*>(b2);  // d2 == nullptr

The dynamic cast doesn’t tell us what the real type of the object is; rather, it allows us to ask the question—Is the real type Derived? If our guess at the type is correct, the cast succeeds and returns the pointer to the derived object. If the real type is something else, the cast fails and returns a null pointer. The dynamic cast can also be used with references, with similar effects, save one—there’s no null reference. A function that returns a reference must always return a reference to some valid object. Since the dynamic cast can’t return a reference to a valid object if the requested type doesn’t match the actual type. The only alternative is to throw an exception.

For performance-conscious code, it is important to be aware of the run-time cost of the dynamic cast. Naively, one might think that a virtual function call and a dynamic cast take about the same time: both boil down to the question – is this pointer to Base really a pointer to Derived? A simple benchmark shows that this is not so:

// Example 02_dynamic_cast.C
class Base {
  protected:
  int i = 0;
  public:
  virtual ~Base() {}
  virtual int f() { return ++i; }
};
class Derived : public Base {
  int f() override { return --i; }
};
Derived* p = new Derived;
// Measure the runtime of p->f();
// Measure the runtime of dynamic_cast<Derived*>(p);

The benchmark results should look something like this (the absolute numbers will depend on the hardware): 1 nanosecond for the virtual call, and 5 to 10 nanoseconds for the dynamic cast. Why is the dynamic cast so expensive? We need to learn more about the hierarchies before we can answer this question.

So far, we’ve limited ourselves to only one base class. While it’s much easier to think about class hierarchies if we imagine them as trees, with the base class and the root and branches where multiple classes are derived from the same base, C++ doesn’t impose such limitations. Next, we’ll learn about inheriting from several base classes at once.

You have been reading a chapter from
Hands-On Design Patterns with C++ - Second Edition
Published in: Jul 2023
Publisher: Packt
ISBN-13: 9781804611555
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime