Hands-On Design Patterns with C++
eBook - ePub

Hands-On Design Patterns with C++

Solve common C++ problems with modern design patterns and build robust applications

Fedor G. Pikus

Compartir libro
  1. 512 páginas
  2. English
  3. ePUB (apto para móviles)
  4. Disponible en iOS y Android
eBook - ePub

Hands-On Design Patterns with C++

Solve common C++ problems with modern design patterns and build robust applications

Fedor G. Pikus

Detalles del libro
Vista previa del libro
Índice
Citas

Información del libro

A comprehensive guide with extensive coverage on concepts such as OOP, functional programming, generic programming, and STL along with the latest features of C++

Key Features

  • 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

Book 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.

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

Who this book is 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.

Preguntas frecuentes

¿Cómo cancelo mi suscripción?
Simplemente, dirígete a la sección ajustes de la cuenta y haz clic en «Cancelar suscripción». Así de sencillo. Después de cancelar tu suscripción, esta permanecerá activa el tiempo restante que hayas pagado. Obtén más información aquí.
¿Cómo descargo los libros?
Por el momento, todos nuestros libros ePub adaptables a dispositivos móviles se pueden descargar a través de la aplicación. La mayor parte de nuestros PDF también se puede descargar y ya estamos trabajando para que el resto también sea descargable. Obtén más información aquí.
¿En qué se diferencian los planes de precios?
Ambos planes te permiten acceder por completo a la biblioteca y a todas las funciones de Perlego. Las únicas diferencias son el precio y el período de suscripción: con el plan anual ahorrarás en torno a un 30 % en comparación con 12 meses de un plan mensual.
¿Qué es Perlego?
Somos un servicio de suscripción de libros de texto en línea que te permite acceder a toda una biblioteca en línea por menos de lo que cuesta un libro al mes. Con más de un millón de libros sobre más de 1000 categorías, ¡tenemos todo lo que necesitas! Obtén más información aquí.
¿Perlego ofrece la función de texto a voz?
Busca el símbolo de lectura en voz alta en tu próximo libro para ver si puedes escucharlo. La herramienta de lectura en voz alta lee el texto en voz alta por ti, resaltando el texto a medida que se lee. Puedes pausarla, acelerarla y ralentizarla. Obtén más información aquí.
¿Es Hands-On Design Patterns with C++ un PDF/ePUB en línea?
Sí, puedes acceder a Hands-On Design Patterns with C++ de Fedor G. Pikus en formato PDF o ePUB, así como a otros libros populares de Computer Science y Programming in C++. Tenemos más de un millón de libros disponibles en nuestro catálogo para que explores.

Información

Año
2019
ISBN
9781788837958
Edición
1

Policy-Based Design

Policy-based design is one of the most well-known C++ patterns. Since the introduction of the standard template library in 1998, few new ideas have been more influential on the way we design C++ programs than the invention of policy-based design.
A policy-based design is all about flexibility, extensibility, and customization. It is a way to design software that can evolve, and can be adapted to the changing needs, some of which could not even be anticipated at the time when the initial design was conceived. A well-designed policy-based system can remain unchanged at the structural level for many years, and serve the changing needs and new requirements without compromise. Unfortunately, it is also a way to build software that could do all of those things if only there was someone who could figure out how it works. The aim of this chapter is to teach the reader to understand and design the systems of the former kind while avoiding the excesses that lead to the disasters of the latter one.
The following topics will be covered in this chapter:
  • Strategy pattern and policy-based design
  • Compile time policies in C++
  • Implementations of policy-based classes
  • Guidelines for the use of policies

Technical requirements

The example code for this chapter can be found at the following GitHub link: https://github.com/PacktPublishing/Hands-On-Design-Patterns-with-CPP/tree/master/Chapter16.

Strategy pattern and policy-based design

The classic Strategy pattern is a behavioral design pattern that enables the runtime selection of a specific algorithm for a particular behavior, usually from a predefined family of algorithms. This pattern is also known as the policy pattern; the name predates its application to the generic programming in C++. The aim of the Strategy pattern is to allow for more flexibility of the design: in the classic object-oriented Strategy pattern, the decision about which specific algorithm to use is deferred until runtime.
As is the case with many classic patterns, the generic programming in C++ applies the same approach to algorithm selection at compile time—it allows for compile-time customization of specific aspects of the system behavior by selecting from a family of related, compatible algorithms. We will now learn the basics of implementing classes with policies in C++, then proceed to study more complex and varied approaches to policy-based design.

Foundations of policy-based design

The Strategy pattern should be considered whenever we design a system that does certain operations, but the exact implementation of these operations is uncertain, varied, or can change after the system is implemented—in other words, when we know the answer to what the system must do, but not how. Similarly, the compile-time strategy, or a policy, is a way to implement a class that has a specific function (what), but there is more than one way to implement that function (how).
Throughout this chapter, we will, to illustrate different ways to use policies, design a smart pointer class. A smart pointer has many other required and optional features besides policies, and we will not cover all of them—for a complete implementation of a smart pointer, the reader is referred to such examples as the C++ standard smart pointers (unique_ptr and shared_ptr), Boost smart pointers, or the Loki smart pointer (http://loki-lib.sourceforge.net/). The material presented in this chapter will help the reader to understand the choices made by the implementers of these libraries, as well as how to design their own policy-based classes.
A very minimal initial implementation of a smart pointer may look like this:
template <typename T>
class SmartPtr {
public:
explicit SmartPtr(T* p = nullptr)
: p_(p) {}
~SmartPtr() {
delete p_;
}
T* operator->() { return p_; }
const T* operator->() const { return p_; }
T& operator*() { return *p_; }
const T& operator*() const { return *p_; }
private:
T* p_;
SmartPtr(const SmartPtr&) = delete;
SmartPtr& operator=(const SmartPtr&) = delete;
};
This pointer has a constructor from the raw pointer of the same type and the usual (for a pointer) operators, that is, * and ->. The most interesting part here is the destructor—when the pointer is destroyed, it automatically deletes the object as well (it is not necessary to check the pointer for the null value before deleting it; the operator delete is required to accept a null pointer and do nothing). It follows, therefore, that the expected use of this smart pointer is as follows:
Class C { ..... };
{
SmartPtr<C> p(new C);
..... use p .....
} // Object *p is deleted automatically
This is a basic example of the RAII class—the RAII object—the smart pointer, in our case—owns the resource (the constructed object) and releases (deletes) it when the owning object itself is deleted. The common applications, which were considered in detail in Chapter 5, A Comprehensive Look at RAII, focus on ensuring that the object that was constructed in the scope is deleted when the program exits this scope, no matter how the latter is accomplished (for example, if an exception is thrown somewhere in the middle of the code, the RAII destructor guarantees that the object is destroyed).
Two more member functions of the smart pointer are noted, not for their implementation, but for their absence—the pointer is made non-copyable as both its copy constructor and the assignment operator are disabled. This detail, which is sometimes overlooked, is of crucial importance for any RAII class—since the destructor of the pointer deletes the owned object, there should never be two pointers that point to, and will attempt to delete, the same object.
The pointer we have here is functional, but the implementation is constraining. In particular, it can own and delete only an object that was constructed with the standard operator new, and only a single object. While it could capture a pointer that was obtained from a custom operator new, or a pointer to an array of elements, it does not properly delete such objects.
We could implement a different smart pointer for objects that are created on a user-defined heap, and another one for objects that are created in client-managed memory, and so on, one for every type of object construction with its corresponding way of deletion. Most of the code for these pointers would be duplicated—they are all pointers, and the entire pointer-like API will have to be copied into every class. We can observe that all of these different classes are, fundamentally, of the same kind—the answer to the question what is this type? is always the same—it's a smart pointer. The only difference is in how the deletion is implemented. This common intent with a difference in one particular aspect of the behavior suggests the use of the Strategy pattern. We can implement a more general smart pointer where the details of how to handle the deletion of the object are delegates to one of any number of deletion policies:
template <typename T, typename DeletionPolicy>
class SmartPtr {
public:
explicit SmartPtr(
T* p = nullptr,
const DeletionPolicy& deletion_policy = DeletionPolicy()
) : p_(p),
deletion_policy_(deletion_policy)
{}
~SmartPtr() {
deletion_policy_(p_);
}
T* operator->() { return p_; }
const T* operator->() const { return p_; }
T& operator*() { return *p_; }
const T& operator*() const { return *p_; }
private:
T* p_;
DeletionPolicy deletion_policy_;
SmartPtr(const SmartPtr&) = delete;
SmartPtr& operator=(const SmartPtr&) = delete;
};
The deletion policy is an additional template parameter, and an object of the type of the deletion policy is passed to the constructor of the smart pointer (by default, such an object is default-constructed). The deletion policy object is stored in the smart pointer and is used in its destructor to delete the object that's being pointed to by the pointer.
The only requirement on the deletion policy type is that it should be callable—the policy is invoked, just like a function with one argument, and the pointer to the object that must be deleted. For example, the behavior of our original pointer that called operator delete on the object can be replicated with the following deletion policy:
template <typename T>
struct DeleteByOperator {
void operator()(T* p) const {
delete p;
}
};
To use this policy, we must specify its type when constructing the smart pointer, and, optionally, pass an object of this type to the constructor, although in this case, the default constructed object will work fine:
class C { ..... };
SmartPtr<C, DeleteByOperator<C>> p(new C);
If the deletion policy does not match the object type, a syntax error will be reported for the invalid call to operator().
Other deletion policies are needed for objects that were allocated in different ways. For example, if an object is created on a user-given heap object whose interface includes the member functions allocate() and deallocate() to, respectively, allocate and free memory, we can use the following heap deletion policy:
template <typename T>
struct DeleteHeap {
explicit DeleteHeap(Heap& heap)
: heap_(heap) {}
void operator()(T* p) const {
p->~T();
heap_.deallocate(p);
}
private:
Heap& heap_;
};
On the other hand, if an object is constructed in some memory that is managed separately by the caller, then only the destructor of the object needs to be called:
template <typename T>
struct DeleteDestructorOnly {
void operator()(T* p) const {
p->~T();
}
};
We mentioned earlier that, because the policy is used as a callable entity, deletion_policy_(p_), it can be of any type that can be called like a function. That includes the actual function:
typedef void (*delete_int_t)(int*);
void delete_int(int* p) { delete p; }
SmartPtr<int, delete_int_t> p(new int(42), delete_int);
A template instantiation is also a function and can be used in the same way:
template <typename T> void delete_T(T* p) { delete p; }
SmartPtr<int, delete_int_t> p(new int(42), delete_T<int>);
Of all the possible deletion policies, one is often the most commonly used. In most programs, it will likely be deletion by the default operator delete function. If this is so, it makes sense to avoid specifying this one policy every time it's used and make it the default:
template <typename T, typename DeletionPolicy = DeleteByOperator<T>>
class SmartPtr {
.....
};
Now, our policy-based smart pointer can be used in exactly the same way as the original version, with only one deletion option:
SmartPtr<C> p(new C);
Here, the second template parameter is left to its default value, DeleteByOperator<C>, and a default constructed object of this type is passed to the constructor as the default second argument.
At this point, we must caution the reader against a subtle mistake that could be made when implementing such policy-based classes. Note that the policy object is captured in the constructor of the smart pointer by a const reference:
explicit SmartPtr(
T* p = nullptr,
const DeletionPolicy& deletion_policy = DeletionPolicy());
The const reference here is important since a non-const reference cannot be bound to a temporary object (we will consider the r-value references later in this section). However, the policy is stored in the object itself by value, and, thus, a copy of the policy object must be made:
template <typename T, typename DeletionPolicy = DeleteByOperator<T>>
class SmartPtr {
.....
private:
DeletionPolicy deletion_policy_;
};
It may be tempting to avoid the copy and capture the policy by re...

Índice