Book Image

Modern C++ Programming Cookbook - Third Edition

By : Marius Bancila
Book Image

Modern C++ Programming Cookbook - Third Edition

By: Marius Bancila

Overview of this book

The updated third edition of Modern C++ Programming Cookbook addresses the latest features of C++23, such as the stack library, the expected and mdspan types, span buffers, formatting library improvements, and updates to the ranges library. It also gets into more C++20 topics not previously covered, such as sync output streams and source_location. The book is organized in the form of practical recipes covering a wide range of real-world problems. It gets into the details of all the core concepts of modern C++ programming, such as functions and classes, iterators and algorithms, streams and the file system, threading and concurrency, smart pointers and move semantics, and many others. You will cover the performance aspects of programming in depth, and learning to write fast and lean code with the help of best practices. You will explore useful patterns and the implementation of many idioms, including pimpl, named parameter, attorney-client, and the factory pattern. A chapter dedicated to unit testing introduces you to three of the most widely used libraries for C++: Boost.Test, Google Test, and Catch2. By the end of this modern C++ programming book, you will be able to effectively leverage the features and techniques of C++11/14/17/20/23 programming to enhance the performance, scalability, and efficiency of your applications.
Table of Contents (15 chapters)
13
Other Books You May Enjoy
14
Index

Using override and final for virtual methods

Unlike other similar programming languages, C++ does not have a specific syntax for declaring interfaces (which are basically classes with pure virtual methods only) and also has some deficiencies related to how virtual methods are declared. In C++, the virtual methods are introduced with the virtual keyword. However, the keyword virtual is optional for declaring overrides in derived classes, which can lead to confusion when dealing with large classes or hierarchies. You may need to navigate throughout the hierarchy up to the base to figure out whether a function is virtual or not. Conversely, sometimes, it is useful to make sure that a virtual function or even a derived class can no longer be overridden or derived further. In this recipe, we will see how to use the C++11 special identifiers override and final to declare virtual functions or classes.

Getting ready

You should be familiar with inheritance and polymorphism in C++ and concepts such as abstract classes, pure specifiers, and virtual and overridden methods.

How to do it...

To ensure the correct declaration of virtual methods both in base and derived classes, also ensuring that you increase readability, do the following:

  • Aim to use the virtual keyword when declaring virtual functions in derived classes that are supposed to override virtual functions from a base class.
  • Always use the override special identifier after the declarator part of a virtual function’s declaration or definition:
    class Base
    {
      virtual void foo() = 0;
      virtual void bar() {}
      virtual void foobar() = 0;
    };
    void Base::foobar() {}
    class Derived1 : public Base
    {
      virtual void foo() override = 0;
      virtual void bar() override {}
      virtual void foobar() override {}
    };
    class Derived2 : public Derived1
    {
      virtual void foo() override {}
    };
    

The declarator is the part of the type of a function that excludes the return type.

To ensure that functions cannot be overridden further or that classes cannot be derived any more, use the final special identifier, like this:

  • After the declarator part of a virtual function declaration or definition to prevent further overrides in a derived class:
    class Derived2 : public Derived1
    {
      virtual void foo() final {}
    };
    
  • After the name of a class in the declaration of the class to prevent further derivations of the class:
    class Derived4 final : public Derived1
    {
      virtual void foo() override {}
    };
    

How it works...

The way override works is very simple; in a virtual function declaration or definition, it ensures that the function actually overrides a base class function; otherwise, the compiler will trigger an error.

It should be noted that both the override and final special identifiers are special identifiers that have a meaning only in a member function declaration or definition. They are not reserved keywords and can still be used elsewhere in a program as user-defined identifiers.

Using the override special identifier helps the compiler detect situations where a virtual method does not override another one, as shown in the following example:

class Base
{
public:
  virtual void foo() {}
  virtual void bar() {}
};
class Derived1 : public Base
{
public:
  void foo() override {}
  // for readability use the virtual keyword
  virtual void bar(char const c) override {}
  // error, no Base::bar(char const)
};

Without the presence of the override specifier, the virtual bar(char const) method of the Derived1 class would not be an overridden method but, instead, an overload of the bar() from Base.

The other special identifier, final, is used in a member function declaration or definition to indicate that the function is virtual and cannot be overridden in a derived class. If a derived class attempts to override the virtual function, the compiler triggers an error:

class Derived2 : public Derived1
{
  virtual void foo() final {}
};
class Derived3 : public Derived2
{
  virtual void foo() override {} // error
};

The final specifier can also be used in a class declaration to indicate that it cannot be derived:

class Derived4 final : public Derived1
{
  virtual void foo() override {}
};
class Derived5 : public Derived4 // error
{
};

Since both override and final have this special meaning when used in the defined context and are not, in fact, reserved keywords, you can still use them anywhere else in the C++ code. This ensures that existing code written before C++11 does not break because of the use of these names for identifiers:

class foo
{
  int final = 0;
  void override() {}
};

Although the recommendation given earlier suggested using both virtual and override in the declaration of an overridden virtual method, the virtual keyword is optional and can be omitted to shorten the declaration. The presence of the override specifier should be enough to indicate to the reader that the method is virtual. This is rather a matter of personal preference and does not affect the semantics.

See also

  • Chapter 10, Static polymorphism with the curiously recurring template pattern, to learn how the CRTP pattern helps with implementing polymorphism at compile time