![](/user_photo/_userpic.png)
- •Table of Contents
- •About the Author
- •About the Technical Reviewer
- •Acknowledgments
- •Software Entropy
- •Clean Code
- •C++11: The Beginning of a New Era
- •Who This Book Is For
- •Conventions Used in This Book
- •Sidebars
- •Notes, Tips, and Warnings
- •Code Samples
- •Coding Style
- •C++ Core Guidelines
- •Companion Website and Source Code Repository
- •UML Diagrams
- •The Need for Testing
- •Unit Tests
- •What About QA?
- •Rules for Good Unit Tests
- •Test Code Quality
- •Unit Test Naming
- •Unit Test Independence
- •One Assertion per Test
- •Independent Initialization of Unit Test Environments
- •Exclude Getters and Setters
- •Exclude Third-Party Code
- •Exclude External Systems
- •What Do We Do with the Database?
- •Don’t Mix Test Code with Production Code
- •Tests Must Run Fast
- •How Do You Find a Test’s Input Data?
- •Equivalence Partitioning
- •Boundary Value Analysis
- •Test Doubles (Fake Objects)
- •What Is a Principle?
- •KISS
- •YAGNI
- •It’s About Knowledge!
- •Building Abstractions Is Sometimes Hard
- •Information Hiding
- •Strong Cohesion
- •Loose Coupling
- •Be Careful with Optimizations
- •Principle of Least Astonishment (PLA)
- •The Boy Scout Rule
- •Collective Code Ownership
- •Good Names
- •Names Should Be Self-Explanatory
- •Use Names from the Domain
- •Choose Names at an Appropriate Level of Abstraction
- •Avoid Redundancy When Choosing a Name
- •Avoid Cryptic Abbreviations
- •Avoid Hungarian Notation and Prefixes
- •Avoid Using the Same Name for Different Purposes
- •Comments
- •Let the Code Tell the Story
- •Do Not Comment Obvious Things
- •Don’t Disable Code with Comments
- •Don’t Write Block Comments
- •Don’t Use Comments to Substitute Version Control
- •The Rare Cases Where Comments Are Useful
- •Documentation Generation from Source Code
- •Functions
- •One Thing, No More!
- •Let Them Be Small
- •“But the Call Time Overhead!”
- •Function Naming
- •Use Intention-Revealing Names
- •Parameters and Return Values
- •Avoid Flag Parameters
- •Avoid Output Parameters
- •Don’t Pass or Return 0 (NULL, nullptr)
- •Strategies for Avoiding Regular Pointers
- •Choose simple object construction on the stack instead of on the heap
- •In a function’s argument list, use (const) references instead of pointers
- •If it is inevitable to deal with a pointer to a resource, use a smart one
- •If an API returns a raw pointer...
- •The Power of const Correctness
- •About Old C-Style in C++ Projects
- •Choose C++ Strings and Streams over Old C-Style char*
- •Use C++ Casts Instead of Old C-Style Casts
- •Avoid Macros
- •Managing Resources
- •Resource Acquisition Is Initialization (RAII)
- •Smart Pointers
- •Unique Ownership with std::unique_ptr<T>
- •Shared Ownership with std::shared_ptr<T>
- •No Ownership, but Secure Access with std::weak_ptr<T>
- •Atomic Smart Pointers
- •Avoid Explicit New and Delete
- •Managing Proprietary Resources
- •We Like to Move It
- •What Are Move Semantics?
- •The Matter with Those lvalues and rvalues
- •rvalue References
- •Don’t Enforce Move Everywhere
- •The Rule of Zero
- •The Compiler Is Your Colleague
- •Automatic Type Deduction
- •Computations During Compile Time
- •Variable Templates
- •Don’t Allow Undefined Behavior
- •Type-Rich Programming
- •Know Your Libraries
- •Take Advantage of <algorithm>
- •Easier Parallelization of Algorithms Since C++17
- •Sorting and Output of a Container
- •More Convenience with Ranges
- •Non-Owning Ranges with Views
- •Comparing Two Sequences
- •Take Advantage of Boost
- •More Libraries That You Should Know About
- •Proper Exception and Error Handling
- •Prevention Is Better Than Aftercare
- •No Exception Safety
- •Basic Exception Safety
- •Strong Exception Safety
- •The No-Throw Guarantee
- •An Exception Is an Exception, Literally!
- •If You Can’t Recover, Get Out Quickly
- •Define User-Specific Exception Types
- •Throw by Value, Catch by const Reference
- •Pay Attention to the Correct Order of Catch Clauses
- •Interface Design
- •Attributes
- •noreturn (since C++11)
- •deprecated (since C++14)
- •nodiscard (since C++17)
- •maybe_unused (since C++17)
- •Concepts: Requirements for Template Arguments
- •The Basics of Modularization
- •Criteria for Finding Modules
- •Focus on the Domain of Your Software
- •Abstraction
- •Choose a Hierarchical Decomposition
- •Single Responsibility Principle (SRP)
- •Single Level of Abstraction (SLA)
- •The Whole Enchilada
- •Object-Orientation
- •Object-Oriented Thinking
- •Principles for Good Class Design
- •Keep Classes Small
- •Open-Closed Principle (OCP)
- •A Short Comparison of Type Erasure Techniques
- •Liskov Substitution Principle (LSP)
- •The Square-Rectangle Dilemma
- •Favor Composition over Inheritance
- •Interface Segregation Principle (ISP)
- •Acyclic Dependency Principle
- •Dependency Inversion Principle (DIP)
- •Don’t Talk to Strangers (The Law of Demeter)
- •Avoid Anemic Classes
- •Tell, Don’t Ask!
- •Avoid Static Class Members
- •Modules
- •The Drawbacks of #include
- •Three Options for Using Modules
- •Include Translation
- •Header Importation
- •Module Importation
- •Separating Interface and Implementation
- •The Impact of Modules
- •What Is Functional Programming?
- •What Is a Function?
- •Pure vs Impure Functions
- •Functional Programming in Modern C++
- •Functional Programming with C++ Templates
- •Function-Like Objects (Functors)
- •Generator
- •Unary Function
- •Predicate
- •Binary Functors
- •Binders and Function Wrappers
- •Lambda Expressions
- •Generic Lambda Expressions (C++14)
- •Lambda Templates (C++20)
- •Higher-Order Functions
- •Map, Filter, and Reduce
- •Filter
- •Reduce (Fold)
- •Fold Expressions in C++17
- •Pipelining with Range Adaptors (C++20)
- •Clean Code in Functional Programming
- •The Drawbacks of Plain Old Unit Testing (POUT)
- •Test-Driven Development as a Game Changer
- •The Workflow of TDD
- •TDD by Example: The Roman Numerals Code Kata
- •Preparations
- •The First Test
- •The Second Test
- •The Third Test and the Tidying Afterward
- •More Sophisticated Tests with a Custom Assertion
- •It’s Time to Clean Up Again
- •Approaching the Finish Line
- •Done!
- •The Advantages of TDD
- •When We Should Not Use TDD
- •TDD Is Not a Replacement for Code Reviews
- •Design Principles vs Design Patterns
- •Some Patterns and When to Use Them
- •Dependency Injection (DI)
- •The Singleton Anti-Pattern
- •Dependency Injection to the Rescue
- •Adapter
- •Strategy
- •Command
- •Command Processor
- •Composite
- •Observer
- •Factories
- •Simple Factory
- •Facade
- •The Money Class
- •Special Case Object (Null Object)
- •What Is an Idiom?
- •Some Useful C++ Idioms
- •The Power of Immutability
- •Substitution Failure Is Not an Error (SFINAE)
- •The Copy-and-Swap Idiom
- •Pointer to Implementation (PIMPL)
- •Structural Modeling
- •Component
- •Interface
- •Association
- •Generalization
- •Dependency
- •Template and Template Binding
- •Behavioral Modeling
- •Activity Diagram
- •Action
- •Control Flow Edge
- •Other Activity Nodes
- •Sequence Diagram
- •Lifeline
- •Message
- •State Diagram
- •State
- •Transitions
- •External Transitions
- •Internal Transitions
- •Trigger
- •Stereotypes
- •Bibliography
- •Index
Chapter 9 Design Patterns and Idioms
Additionally, the Clazz::swap() member function now makes it very easy to implement a move constructor:
class Clazz { public:
// ...
Clazz(Clazz&& other) noexcept { swap(other);
}
// ...
};
Of course, the major goal in good class design should be that it is not necessary to implement explicit copy constructors and assignment operators (recall the Rule of Zero). But when you are forced to do it, you should remember the copy-and-swap idiom.
Pointer to Implementation (PIMPL)
The last section of this chapter is dedicated to an idiom with the funny acronym, PIMPL. PIMPL stands for Pointer to Implementation; and the idiom is also known as
Handle Body, the Compilation Firewall, or Cheshire Cat technique (The Cheshire Cat is a fictional character, a grinning cat, from Lewis Carroll’s novel Alice’s Adventures in Wonderland.) It has, by the way, some similarities with the Bridge pattern described in [Gamma95].
The intent of the PIMPL could be formulated as follows:
“Remove compilation dependencies on internal class implementation details by relocating them into a hidden implementation class and thus improve compile times.”
Let’s look at an excerpt from the Customer class, a class that we’ve seen in many examples before. See Listing 9-49.
Listing 9-49. An Excerpt from the Contents of the Customer.h Header File
#ifndef CUSTOMER_H_ #define CUSTOMER_H_
#include "Address.h" #include "Identifier.h"
447
Chapter 9 Design Patterns and Idioms
#include <string>
class Customer { public:
Customer();
virtual ~Customer() = default; std::string getFullName() const;
void setShippingAddress(const Address& address); // ...
private:
Identifier customerId; std::string forename; std::string surname; Address shippingAddress;
};
#endif /* CUSTOMER_H_ */
Let’s assume that this is a central business entity in our commercial software system and that it is used (#include "Customer.h") by many other classes. When this header file changes, any files that use that file will need to be recompiled, even if only one private member variable is added, renamed, etc.
In order to reduce these recompilations to the absolute minimum, the PIMPL idiom comes in to play.
First we rebuild the class interface of the Customer class, as shown in Listing 9-50.
Listing 9-50. The Altered Customer.h Header File
#ifndef CUSTOMER_H_ #define CUSTOMER_H_
#include <memory> #include <string>
class Address;
class Customer { public:
Customer();
448
Chapter 9 Design Patterns and Idioms
virtual ~Customer(); std::string getFullName() const;
void setShippingAddress(const Address& address); // ...
private: class Impl;
std::unique_ptr<Impl> impl;
};
#endif /* CUSTOMER_H_ */
It is conspicuous that all previous private member variables, as well as their associated include-directives, have now disappeared. Instead, a forward declaration for a class named Impl, as well as a std::unique_ptr<T> to this forward-declared class, is present.
Let’s look at the corresponding implementation file, shown in Listing 9-51.
Listing 9-51. The Contents of the Customer.cpp File
#include "Customer.h"
#include "Address.h" #include "Identifier.h"
class Customer::Impl final { public:
std::string getFullName() const;
void setShippingAddress(const Address& address);
private:
Identifier customerId; std::string forename; std::string surname; Address shippingAddress;
};
std::string Customer::Impl::getFullName() const { return forename + " " + surname;
}
449
Chapter 9 Design Patterns and Idioms
void Customer::Impl::setShippingAddress(const Address& address) { shippingAddress = address;
}
// Implementation of class Customer starts here...
Customer::Customer() : impl { std::make_unique<Customer::Impl>() } { }
Customer::~Customer() = default;
std::string Customer::getFullName() const { return impl->getFullName();
}
void Customer::setShippingAddress(const Address& address) { impl->setShippingAddress(address);
}
In the upper part of the implementation file (up to the source code comment), we can see the Customer::Impl class. Everything has been relocated in this class, which formerly was done directly by the Customer class. Here we also find all member variables.
In the lower section (beginning with the comment), we now find the implementation of the Customer class. The constructor creates an instance of Customer::Impl and holds it in the smart pointer impl. As to the rest, any call of the API of the Customer class is delegated to the internal implementation object.
If something has to be changed in the internal implementation in Customer::Impl, the compiler must only compile Customer.h/Customer.cpp, and then the linker can start its work immediately. Such changes do not have an effect on the outside, and a time- consuming compilation of the almost entire project is avoided.
450
![](/html/75672/2303/html_3iAzqyDIia.Sav3/htmlconvd-3gbB7Q460x1.jpg)
APPENDIX A
Small UML Guide
“Learn the rules so you know how to break them properly.”
―Rule no. 5 of the Dalai Lama’s “18 Rules for Living”
The OMG Unified Modeling Language (OMG UML)1 is a standardized graphical language used to create models of software and other systems. Its main purpose is to enable developers, software architects, and other stakeholders to design, specify, visualize, construct, and document artifacts of a software system. The language supports both the modeling of structures (the building blocks of a software and their relationships), as well as their behavior (how those building blocks interact and collaborate at runtime). Well-crafted UML models support the discussion between
different stakeholders, serve as an aid to clarify requirements and other issues related to the system of interest, and can capture design decisions.
The vocabulary range of the UML is very extensive. The language offers 15 different diagram types for different purposes. However, as with any other language, it is not usually necessary to use all the vocabulary you know in daily communication. The “art of omission” is also essential here. In practice, there is always a limitation on the language elements you need, and a limitation on a few diagram types.
This appendix provides a brief overview of that subset of UML notations that are used in this book. Each UML element is illustrated (syntax) and briefly explained (semantic). The short definition for an element is based on the current UML specification [OMG15], which can be downloaded for free from OMG’s website. An in- depth introduction to the Unified Modeling Language should be made with the help of appropriate literature, or by taking a course at a training provider.
1OMG, Unified Modeling Language, and UML are registered trademarks of the Object Management Group, Inc.
451
© Stephan Roth 2021
S. Roth, Clean C++20, https://doi.org/10.1007/978-1-4842-5949-8