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

Hands-On Design Patterns with C++: Solve common C++ problems with modern design patterns and build robust applications

eBook
€22.99 €32.99
Paperback
€41.99
Subscription
Free Trial
Renews at €18.99p/m

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
Table of content icon View table of contents Preview book icon Preview Book

Hands-On Design Patterns with C++

An Introduction to Inheritance and Polymorphism

C++ is, first and foremost, an object-oriented language, and objects are the fundamental building blocks of a C++ program. Class hierarchies are used to express relationships and interactions between different parts of a software system, define and implement the interfaces between components, and organize data and code. While this isn't a book for teaching C++, the aim of this chapter is to give the reader enough knowledge about C++ language features as they relate to classes and inheritance, which will be used in later chapters. To that end, we don't attempt to completely describe the C++ tools for working with classes but introduce the concepts and language constructs that will be used throughout this book.

The following topics will be covered in this chapter:

  • What are classes and what is their role in C++?
  • What are class hierarchies and how does C++ use inheritance?
  • What is runtime polymorphism and how is it used in C++?

Classes and objects

Object-oriented programming is a way to structure a program by combining the algorithms and the data that the algorithms operate on into single entities called objects. Most object-oriented languages, including C++, are class-based. A class is a definition of an object—it describes the algorithms and the data, its format, and relations to other classes. An object is a concrete instantiation of a class, that is, a variable. An object has an address, which is a location in memory. A class is a user-defined type. In general, any number of objects can be instantiated from the definition provided by the class (some classes limit the number of objects that can be created, but this is an exception, not the norm).

In C++, the data contained in a class is organized as a collection of data members, variables of different types. The algorithms are implemented as functions—the methods of the class. While there's no language requirement that the data members of a class should be somehow relevant to the implementation of its methods, it's one of the signs of good design when the data is well encapsulated in the classes, and the methods have limited interaction with external data.

This concept of encapsulation is central to the classes in C++—the language allows us to control which data members and methods are public—visible outside of the class, and which are internalprivate to the class. A well-designed class has mostly, or only, private data members, and the only public methods are those needed to express the public interface of the class—in other words, what the class does. This public interface is like a contract—the class designer promises that this class provides certain features and operations. The private data and methods of the class are part of the implementation, and they can be changed as long as the public interface, the contract we've committed to, remains valid. For example, the following class represents a rational number and supports the increment operation, as exposed by its public interface:

class Rational {
public:
Rational& operator+=(const Rational& rhs);
};

A well-designed class doesn't expose any more of the implementation details than it has to through its public interface. The implementation isn't part of the contract, although the documented interface may impose some restrictions on it. For example, if we promise that all rational numbers don't contain any common multipliers in the numerator and denomination, the addition should include the step of canceling them. That would be a good use of a private member function—the implementation of several other operations will need to call it, but the client of the class never needs to call it because every rational number is already reduced to its lowest terms before it's exposed to the callers:

class Rational {
public:
Rational& operator+=(const Rational& rhs);
private:
long n_; // numerator
long d_; // denominator
void reduce();
};
Rational& Rational::operator+=(const Rational& rhs) {
n_ = n_*rhs.d_ + rhs.n_*d_;
d_ = d_*rhs.d_;
reduce();
return *this;
}
Rational a, b;
a += b;

The class methods have special access to the data members—they can access the private data of the class. Note the distinction between the class and the object here—operator+=() is a method of the Rational class and is invoked on the object, a. However, it has access to the private data of the b object as well, because a and b are objects of the same class. If a member function references a class member by name without any additional qualifiers, then it's accessing a member of the same class it's invoked on (we can make it explicit by writing this->n_ and this->d_). Accessing members of another object of the same class requires a pointer or a reference to that object, but is otherwise not restricted, as would have been the case if we tried to access a private data member from a non-member function.

By the way, C++ also supports C-style structs. But in C++, a struct isn't limited to just an aggregate of data members—it can have methods, public and private access modifiers, and anything else classes have. From a language point of view, the only difference between a class and a struct is the default access—in a class, all members and methods are private by default, while in a struct they're public. Beyond that, the use of structs instead of classes in a matter of convention—traditionally, structs are used for C-style structs (structs that would be legal in C) as well as almost C-style structs, for example, a struct with only a constructor added. Of course, this boundary isn't precise and is a matter of coding styles and practices in each project or team.

In addition to the methods and data members we've seen, C++ also supports static data and methods. A static method is very similar to a regular non-member function—it isn't invoked on any particular object, and the only way it can get access to an object of any type is through its arguments. However, unlike a non-member function, a static method retains its privileged access to the private data of the class.

Classes by themselves are a useful way to group together the algorithms and the data they operate on and to limit access to some data. However, the most powerful object-oriented features of C++ are inheritance and the resulting class hierarchies.

Inheritance and class hierarchies

Class hierarchies in C++ serve a dual purpose. On the one hand, they allow us to express relations between objects. On the other hand, they let us compose more complex types from simpler ones. Both uses are accomplished through inheritance.

The concept of inheritance is central to the C++ use of classes and objects. Inheritance allows us to define new classes as extensions of existing ones. When a derived class is inherited from the base class, it contains, in some form, all of the data and the algorithms that were in the base class, and it adds some of its own. In C++, it's important to distinguish between two primary types of inheritance—public and private.

Public inheritance inherits the public interface of the class. It also inherits the implementation—the data members of the base class are also a part of the derived class. But the inheritance of the interface is what distinguishes public inheritance—the derived class has, as a part of its public interface, the public member functions of the base class.

Remember that the public interface is like a contract—we promise to the clients of the class that it supports certain operations, maintains some invariants, and obeys the specified restrictions. By publicly inheriting from the base class, we bind the derived class to the same contract (plus any extensions of the contract, should we decide to define additional public interfaces). Because the derived class also respects the interface contract of the base class, we could use a derived class in any place in the code where a base class is expected—we would not be able to use any of the extensions to the interface (the code expects the base class, we don't know about any extensions at that point), but the base class interface and its restrictions have to be valid.

This is often expressed as the is-a principle—an instance of a derived class is also an instance of the base class. However, the way we interpret the is-a relationship in C++ isn't exactly intuitive. For example, is a square a rectangle? If it is, then we can derive the Square class from the Rectangle class:

class Rectangle {
public:
double Length() const { return length_; }
double Width() const { return width_; }
...
private:
double l_;
double w_;
};
class Square : public Rectangle {
...
};

Right away, there's something that doesn't seem right—the derived class has two data members for dimensions, but it really needs only one. We would have to somehow enforce that they're always the same. This doesn't seem so bad—the Rectangle class has the interface that allows for any positive values of length and width, and the Square imposes additional restrictions. But it's worse than that—the Rectangle class has a contract that allows the user to make the dimensions different. This can be quite explicit:

class Rectangle {
public:
void Scale(double sl, double sw) { // Scale the dimensions
length_ *= sl;
width_ *= sw;
}
...
};

Now, we have a public method that allows us to distort the rectangle, altering its aspect ratio. Like any other public method, it's inherited by the derived classes, so now the Square class has it too. In fact, by using public inheritance, we assert that a Square object can be used anywhere a Rectangle object is used, without even knowing that it's really a Square. Clearly, this is a promise we can't keep—when the client of our class hierarchy tries to change the aspect ratio of a square, we can't do it. We could ignore the call or report an error at runtime. Either way, we've violated the contract provided by the base class. There's only one solution—in C++, a square isn't a rectangle. Note that a rectangle is usually not a square, either—the contract provided by the Square interface could contain any number of guarantees that we can't maintain if we derive the Rectangle class from Square.

Similarly, a penguin isn't a bird in C++ if the bird interface includes flying. The correct design for such cases usually includes a more abstract base class, Bird, that doesn't make any promises that at least one derived class can't keep (for example, a Bird object doesn't make a guarantee that it can fly). Then, we create intermediate-based classes, such as FlyingBird and FlightlessBird, that are derived from the common base class and serve as base classes for the more specific classes such as Eagle or Penguin. The important lesson here is that whether or not a penguin is a bird in C++ depends on how we define what a bird is, or, in C++ terms, what the public interface of the Bird class is.

Because the public inheritance implies the is-a relationship, the language allows a wide range of conversions between references and pointers to different classes in the same hierarchy. First of all, a conversion from a pointer to a derived class into a pointer to the base class is implicit (this is the same for references):

class Base { ... };
class Derived : public Base { ... };
Derived* d = new Derived;
Base* b = d; // Implicit conversion

This conversion is always valid because an instance of the derived class is also an instance of the base class. The inverse conversion is possible but has to be made explicit:

Base* b = new Derived;    // *b is really Derived
Derived* d = b; // Does not compile, not implicit
Derived* d = static_cast<Derived*>(b); // Explicit conversion

The reason this conversion isn't implicit is that it's valid only if the base class pointer really points to a derived object (otherwise, the behavior is undefined). The programmer, therefore, must explicitly assert, using the static cast, that somehow, through the logic of the program or a prior test or by some other means, it's known that this conversion is valid. If you aren't sure that the conversion is valid, there's a safer way to try it without causing undefined behavior; we'll learn about this in the next section.

The other kind of inheritance in C++ is private inheritance. When inheriting privately, the derived classes don't extend the public interface of the base class—all base class methods become private in the derived class. Any public interface has to be created by the derived class, starting from a clean slate. There's no assumption that an object of the derived class can be used in place of an object of the base class. What the derived class does get from the base class is the implementation details—both the methods and the data members can be used by the derived class to implement its own algorithms. It's said, therefore, that private inheritance implements a has-a relationship—the derived object has an instance of the base class contained inside of it.

The relation of the privately derived class to its base class is, therefore, similar to that of the relationship of a class to its data members. The latter implementation technique is known as composition—an object is composed from any number of other objects, which are all used as its data members. In the absence of any reason to do otherwise, the composition should be preferred to private inheritance. What, then, might be the reasons to use private inheritance? There are several possibilities. First of all, it's possible, within the derived class, to re-expose one of the public member functions of the base class with the help of the using declaration:

class Container : private std::vector<int> {
public:
using std::vector<int>::size;
...
};

This can be useful in rare cases, but it's also equivalent to an inline forwarding function:

class Container {
private:
std::vector<int> v_;
public:
size_t size() const { return v_.size(); }
...
};

Second, a pointer or reference to a derived object can be converted into a pointer or reference to the base object, but only inside a member function of the derived class. Again, the equivalent functionality for composition is provided by taking the address of a data member. So far, we haven't seen a good reason to use private inheritance, and indeed, the common advice is to prefer composition. But the next two reasons are more significant, and either one could be motivation enough to use private inheritance.

One good reason to use private inheritance has to do with the size of the composed or derived objects. It isn't uncommon to have base classes that provide only methods but no data members. Such classes have no data of their own and, therefore, should not occupy any memory. But in C++, they have to be given a non-zero size. This has to do with the requirement that any two different objects or variables have different and unique addresses. Typically, if we have two variables declared one after the other, the address of the second one is the address of the first one, plus the size of the first one:

int x;     // Created at address 0xffff0000, size is 4
int y; // Created at address 0xffff0004

To avoid the need to handle zero-sized objects differently, C++ assigns an empty object the size of one. If such an object is used as a data member of a class, it occupies at least 1 byte (the alignment requirements for the next data member may increase this value). This is wasted memory; it'll never be used for anything. On the other hand, if an empty class is used as a base class, there's no requirement that the base part of an object must have a non-zero size. The entire object of the derived class must have a non-zero size, but the address of a derived object, its base object, and its first data member can all be at the same address. Therefore, it's legal in C++ to allocate no memory for an empty base class, even though sizeof() returns 1 for this class. While legal, such empty base class optimization isn't required and is considered an optimization. Nonetheless, most modern compilers do this optimization:

class Empty {
public:
void useful_function();
};
class Derived : private Empty {
int i;
}; // sizeof(Derived) == 4
class Composed {
int i;
Empty e;
}; // sizeof(Composed) == 8

If we create many derived objects, the memory saved by the empty base optimization can be significant.

The second reason to possibly use private inheritance has to do with virtual functions, and this will be explained in the next section.

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 ...
}
};

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 the 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).

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 FlyingBirt {
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. 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:
virtual 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.

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 type, 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 inhered 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 {
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::f() 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 was public.

In the previous section, we recommended that the 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.

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.

Multiple inheritance

In C++, a class can be derived from several base classes. Going back to our birds, let's make an observation—while flying birds have a lot in common with each other, they also have something in common with other flying animals, specifically, the ability to fly. Since flight isn't limited to birds, we may want to move the data and the algorithms related to processing flight into a separate base class. But there's also no denying that an eagle is a bird. We could express this relation if we used two base classes to construct the Eagle class:

class Eagle : public Bird, public FlyingAnimal { ... };

In this case, the inheritance from both base classes is public, which means that the derived class inherits both interfaces and must fulfill two separate contracts. What happens if both interfaces define a method with the same name? If this method isn't virtual, then an attempt to invoke it on the derived class is ambiguous, and the program doesn't compile. If the method is virtual and the derived class has an override for it, then there's no ambiguity since the method of the derived class is called. Also, Eagle is now both Bird and FlyingAnimal:

Eagle* e = new Eagle;
Bird* b = e;
FlyingAnimal* f = e;

Both conversions from the derived class into the base class pointer are allowed. The reverse conversions must be made explicitly using a static or a dynamic cast. There's another interesting conversion—if we have a pointer to a FlyingAnimal class that's also a Bird class, can we cast from one to the other? Yes, we can with a dynamic cast:

Bird* b = new Eagle;    // Also a FlyingAnimal
FlyingAnimal* f = dynamic_cast<FlyingAnimal*>(b);

When used in this context, the dynamic cast is sometimes called a cross-cast—we aren't casting up or down the hierarchy (between derived and based classes) but across the hierarchy—between the classes on different branches of the hierarchy tree.

Multiple inheritance is often maligned and disfavored in C++. Much of this advice is outdated and stems from the time when compilers implemented multiple inheritance poorly and inefficiently. Today, with modern compilers, this isn't a concern. It's often said that multiple inheritance makes the class hierarchy harder to understand and reason about. Perhaps it would be more accurate to say that it's harder to design a good multiple inheritance hierarchy that accurately reflects the relations between different properties, and that a poorly designed hierarchy is difficult to understand and reason about.

These concerns mostly apply to hierarchies that use public inheritance. Multiple inheritance can be private as well. There's even less reason to use multiple private inheritance instead of composition than there was to use single private inheritance. However, the empty base optimization can be done on multiple empty base classes and remains a valid reason to use private inheritance, if it applies:

class Empty1 {};
class Empty2 {};
class Derived : private Empty1, private Empty2 {
int i;
}; // sizeof(Derived) == 4
class Composed {
int i;
Empty1 e1;
Empty2 e2;
}; // sizeof(Composed) == 8

Multiple inheritance can be particularly effective when the derived class represents a system that combines several unrelated, non-overlapping attributes. We'll encounter such cases throughout this book when we explore various design patterns and their C++ representations.

Summary

While by no means a complete guide or reference to classes and objects, this chapter introduces and explains the concepts the reader will need to understand the examples and explanations in the rest of this book. As our interest is and will be in representing design patterns in C++, this chapter focuses on the proper use of classes and inheritance. We pay particular attention to what relations are expressed through different C++ features—it's through these features we'll express relations and interactions between different components that form a design pattern.

The next chapter will similarly cover knowledge of C++ templates, which will be necessary to understand the subsequent chapters of this book.

Questions

  • What is the importance of objects in C++?
  • What relation is expressed by public inheritance?
  • What relation is expressed by private inheritance?
  • What is a polymorphic object?

Further reading

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Delve into the core patterns and components of C++ in order to master application design
  • Learn tricks, techniques, and best practices to solve common design and architectural challenges
  • Understand the limitation imposed by C++ and how to solve them using design patterns

Description

C++ is a general-purpose programming language designed with the goals of efficiency, performance, and flexibility in mind. Design patterns are commonly accepted solutions to well-recognized design problems. In essence, they are a library of reusable components, only for software architecture, and not for a concrete implementation. The focus of this book is on the design patterns that naturally lend themselves to the needs of a C++ programmer, and on the patterns that uniquely benefit from the features of C++, in particular, the generic programming. Armed with the knowledge of these patterns, you will spend less time searching for a solution to a common problem and be familiar with the solutions developed from experience, as well as their advantages and drawbacks. The other use of design patterns is as a concise and an efficient way to communicate. A pattern is a familiar and instantly recognizable solution to specific problem; through its use, sometimes with a single line of code, we can convey a considerable amount of information. The code conveys: "This is the problem we are facing, these are additional considerations that are most important in our case; hence, the following well-known solution was chosen." By the end of this book, you will have gained a comprehensive understanding of design patterns to create robust, reusable, and maintainable code.

Who is this book for?

This book is for experienced C++ developers and programmers who wish to learn about software design patterns and principles and apply them to create robust, reusable, and easily maintainable apps.

What you will learn

  • Recognize the most common design patterns used in C++
  • Understand how to use C++ generic programming to solve common design problems
  • Explore the most powerful C++ idioms, their strengths, and drawbacks
  • Rediscover how to use popular C++ idioms with generic programming
  • Understand the impact of design patterns on the program's performance
Estimated delivery fee Deliver to Czechia

Premium delivery 7 - 10 business days

€25.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Jan 30, 2019
Length: 512 pages
Edition : 1st
Language : English
ISBN-13 : 9781788832564
Category :
Languages :

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
Estimated delivery fee Deliver to Czechia

Premium delivery 7 - 10 business days

€25.95
(Includes tracking information)

Product Details

Publication date : Jan 30, 2019
Length: 512 pages
Edition : 1st
Language : English
ISBN-13 : 9781788832564
Category :
Languages :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€18.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
€189.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
€264.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total 155.97
Modern C++ Programming Cookbook
€71.99
Hands-On System Programming with C++
€41.99
Hands-On Design Patterns with C++
€41.99
Total 155.97 Stars icon

Table of Contents

20 Chapters
An Introduction to Inheritance and Polymorphism Chevron down icon Chevron up icon
Class and Function Templates Chevron down icon Chevron up icon
Memory Ownership Chevron down icon Chevron up icon
Swap - From Simple to Subtle Chevron down icon Chevron up icon
A Comprehensive Look at RAII Chevron down icon Chevron up icon
Understanding Type Erasure Chevron down icon Chevron up icon
SFINAE and Overload Resolution Management Chevron down icon Chevron up icon
The Curiously Recurring Template Pattern Chevron down icon Chevron up icon
Named Arguments and Method Chaining Chevron down icon Chevron up icon
Local Buffer Optimization Chevron down icon Chevron up icon
ScopeGuard Chevron down icon Chevron up icon
Friend Factory Chevron down icon Chevron up icon
Virtual Constructors and Factories Chevron down icon Chevron up icon
The Template Method Pattern and the Non-Virtual Idiom Chevron down icon Chevron up icon
Singleton - A Classic OOP Pattern Chevron down icon Chevron up icon
Policy-Based Design Chevron down icon Chevron up icon
Adapters and Decorators Chevron down icon Chevron up icon
The Visitor Pattern and Multiple Dispatch Chevron down icon Chevron up icon
Assessments Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Half star icon 4.3
(19 Ratings)
5 star 68.4%
4 star 15.8%
3 star 5.3%
2 star 0%
1 star 10.5%
Filter icon Filter
Top Reviews

Filter reviews by




Zheng Qu Jun 20, 2020
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I learned a lot just by reading. Cannot wait to put them into use.
Amazon Verified review Amazon
Hans Schmid Jul 09, 2019
Full star icon Full star icon Full star icon Full star icon Full star icon 5
You won't find any better book on design patterns in C++ with emphasis on C++. The book is just awesome. Thank you Fedor Pikus for writing the book. I was always struggling with reading advanced, modern C++ code until I found this book. The C++ community owes you one - or two!
Amazon Verified review Amazon
Matthias Sep 15, 2022
Full star icon Full star icon Full star icon Full star icon Full star icon 5
… this is it. The author uses all(?) language features to explain in detail how common programming patterns can be efficiently coded with C++.
Amazon Verified review Amazon
Maxim Rozhkov Aug 29, 2020
Full star icon Full star icon Full star icon Full star icon Full star icon 5
The book covers the most important topics of modern C++: RAII, memory ownership, SFINAE, CRTP, Scope Guard, etc. It begins with explaining C++ tricks in generics in the first part of the book. The second logical part reviews the traditional C++ patterns (that were described in the classical «Design Patterns – Elements of Reusable Object-Oriented Software»). The Fedor’s goal is to improve them by the newest C++ 11/14/17 features.The author made the great work: his explanation is clear and accurate. The examples are short and extremely focused on specific topics.In my opinion, the book is in the same area as the Andrei Alexandrescu's book, named "Modern C++ Design: Generic Programming and Design Patterns Applied", but the Fedor's book is the newest one and much close to a regular developer.
Amazon Verified review Amazon
Manish Sharma Sep 23, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I loved this book and I recommend it to anyone who wants to develop any good software in C++
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact customercare@packt.com with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at customercare@packt.com using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on customercare@packt.com with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on customercare@packt.com within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on customercare@packt.com who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on customercare@packt.com within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela