Preface xv
1
Coding Standards in C++ 23
The difference between good code
and bad code 23
Why coding standards are important 24
Code convention 25
Language features limitations 27
General guidelines 27
Readability, efficiency,
maintainability, and usability 28
Readability 28
Efficiency 29
Maintainability 29
Usability 32
Summary 32
2
Main Software Development Principles 33
SOLID 33
The Single Responsibility Principle 34
The Open-Closed Principle 36
The Liskov Substitution Principle 39
The Dependency inversion principle 42
The KISS principle 44
The KISS and SOLID Principles together 45
Side effects and immutability 46
Con.1 – by default, make objects immutable 46
Con.2 – by default, make member functions
const 47
Con.3 – by default, pass pointers and
references to const 49
Con.4 – use const to define objects with
values that do not change after construction 49
Con.5 – use constexpr for values that can be
computed at compile time 49
Constness and data races 50
Summary 54
3
Causes of Bad Code 55
The need to deliver the product 56
The developer’s personal taste 56
Multiple ways of solving the same
problem in C++ 57
Revisiting Bob and Alice’s example 57
Raw pointers and C functions versus
Standard Library functions 57
Inheritance versus templates 58
Example – handling errors 60
Projects using different approaches 60
Lack of knowledge in C++ 61
Using raw pointers and manual memory
management 61
Incorrect use of smart pointers 62
Efficient use of move semantics 62
Misusing const correctness 63
Inefficient string handling 64
Undefined behavior with lambdas 64
Misunderstanding undefined behavior 65
Misuse of C-style arrays 65
Insufficient pointer usage 66
Building std::shared_ptr 66
Copying std::shared_ptr by value 66
Cyclic dependencies with std::shared_ptr 67
Checking the std::weak_ptr status 68
Summary 68
4
Identifying Ideal Candidates for Rewriting – Patterns
and Anti-Patterns 71
What kind of code is worth rewriting? 72
Smelly code and its basic
characteristics 72
Anti-patterns 89
The pitfalls of magic numbers – a case study
on data chunking 97
Legacy code 99
Summary 103
5
The Significance of Naming 105
General naming principles 106
Descriptiveness 106
Consistency 106
Unambiguity 107
Pronounceability 108
Scope and lifetimes 108
Avoid encoding type or scope information 109
Class and method naming 112
Naming variables 113
Utilize namespaces 114
The use of domain-specific language 116
Balancing long names and comments in code 118
Exploring popular C++ coding
conventions – Google, LLVM, and
Mozilla 119
Summary 120
6
Utilizing a Rich Static Type System in C++ 121
Utilizing Chrono for time duration 122
Improving Pointer Safety with not_
null and std::optional 122
The pitfalls of raw pointers 123
Using not_null from the Guidelines Support
Library 123
Utilizing std::optional for optional values 125
A comparison between raw pointers and
nullptr 125
Leveraging std::expected for expected results
and errors 125
Strong typing with enum class and
scoped enumerations 127
A review of enum class 127
The benefits over traditional enums 127
Real-world scenarios 128
Leveraging the standard library’s
type utilities 128
std::variant – a type-safe union 128
std::any – type-safe containers for any type 129
Advanced type techniques 130
Avoiding common pitfalls in
advanced type usage 132
Writing robust code with type checks 132
Implicit conversions and type coercion 133
Summary 135
7
Classes, Objects, and OOP in C++ 137
Good candidates for classes 138
Cohesion 138
Encapsulation 138
Reusability 138
Abstraction 138
Real-world entities 138
Manage complexity 139
Minimizing class responsibilities through
encapsulation 139
Usage of structs and classes in C++ 143
Common method types in classes – getters
and setters 145
Inheritance in C++ 146
Evolution of inheritance in C++ 149
Implementation of inheritance at the binary
level 149
Pros and cons of inheritance 149
Base class – Discount 153
Derived class – SeasonalDiscount 154
Derived class – ClearanceDiscount 154
Tight coupling problems 155
Solution – decouple with the strategy pattern 155
Templates and generic programming 159
What are templates good for? 159
Generic algorithms 159
Container classes 160
How templates work 162
How templates are instantiated 163
A real-world example of template
usage in C++ 164
Defining currencies 164
Defining assets 167
Using the financial system 168
Disadvantages of using templates in system
design 168
Summary 175
8
Designing and Developing APIs in C++ 177
Principles of minimalistic API design 177
Techniques for achieving minimalism 178
Real-world examples of minimalistic
API design 181
Common pitfalls and how to
avoid them 183
Important caveats of developing
shared libraries in C++ 183
Shared libraries within a single project 183
Shared libraries for wider distribution 184
Example – MessageSender class 184
Summary 188
9
Code Formatting and Naming Conventions 189
Why is code formatting important? 189
Overview of existing tools that
facilitate compliance with coding
conventions 190
cpplint 191
Artistic Style 192
Uncrustify 192
Editor plugins 193
Clang-Format 194
Clang-Format configuration – a
deep dive into customizing your
formatting rules 195
Leveraging existing presets 195
Extending and overriding presets 196
Ignoring specific lines with Clang-Format 197
Endless options for configuration 198
Version control and sharing 198
Integrating Clang-Format into the
build system 198
Clang-Format report examples 200
Extending for code format checks
for CI 204
Clang-Format support across
various editors 205
Checking name styling 206
Integrating Clang-Tidy into the
build system 208
Checking source code name styling
with Clang-Tidy 209
Fixing naming issues automatically 212
Important caveats 213
Example project 214
Clang-Tidy support across
various editors 214
Summary 215
10
Introduction to Static Analysis in C++ 217
The essence of static analysis 217
Leveraging newer compiler versions for
enhanced static analysis 218
Compiler settings to harden
C++ code 218
GCC 219
Clang 219
MSVC 220
Static analysis via multiple compilers 220
Highlighting compiler differences – unused
private members in GCC versus Clang 221
Highlighting compiler differences – compiler
checks for uninitialized variables 221
Exploring static analysis with
Clang-Tidy 223
Categories of checks in Clang-Tidy 223
Expanding Clang-Tidy’s capabilities with
custom checks 224
Fine-tuning Clang-Tidy for customized static
analysis 226
Overview of static analysis tools –
comparing PVS-Studio, SonarQube,
and others to Clang-Tidy 227
PVS-Studio 228
SonarQube 228
Other notable tools 228
Comparison with Clang-Tidy 229
Summary 229
11
Dynamic Analysis 231
Compiler-based dynamic
code analysis 232
ASan 232
LeakSanitizer (LSan) 252
MemorySanitizer (MSan) 253
TSan 255
UBSan 259
Dynamic code analysis
with Valgrind 261
Setting up Valgrind 261
Memcheck – the comprehensive memory
debugger 261
Helgrind – threading error detector 263
Performance impact, fine-tuning, and
limitations 265
Other notable tools in the
Valgrind suite 266
Data Race Detector (DRD) – a thread error
detector 266
Cachegrind 266
Callgrind 266
Massif 266
Dynamic heap analysis tool (DHAT) 266
Summary 267
12
Testing 269
Test-driven development 270
Unit testing in C++ 271
C++ unit testing frameworks 271
Google Test and Google Mock 272
Integrating Google Test into a C++
project 272
Usage of Google Test in
C++ projects 274
Writing a simple test 274
Using a test fixture 275
The main function 276
Running Google Test tests 276
Advanced features of Google Test 278
Using gMock in C++ projects 279
Example of using gMock 280
Mocking non-virtual methods via
dependency injection 283
Mocking with templates 285
The Nice, the Strict, and the Naggy 287
Other notable C++ unit testing
frameworks 291
Catch2 291
Boost.Test 291
Doctest 291
Google Test versus Catch2 versus Boost.Test
versus Doctest 291
Good candidates for unit tests 292
E2E testing in software development 293
E2E testing frameworks 293
When to use E2E testing 293
Situations favoring E2E testing 294
Complex interactions 294
Real-world environment testing 295
Automatic test coverage
tracking tools 296
Automatic test coverage tracking tools with
examples 296
Utilizing hit maps for enhanced test coverage
analysis 297
Recommendations for code coverage 299
Summary 300
13
Modern Approach to Managing Third Parties 301
Overview of linking and shared V
threads::ThreadsS static libraries 302
Managing third-party libraries
in C++ 303
Installing libraries with OS package managers 303
Using Git as a third-party manager via
submodules 304
Using CMake FetchContent to download
libraries 304
Conan – advanced dependency
management 305
Conan configuration and features 305
Library locations and Conan Center 305
Configuring static or dynamic linking 306
Extending Conan with custom packages 306
CMake integration 307
Other build system integration 307
Custom integration 308
Conclusion 308
vcpkg 308
Key differences from Conan 308
Operating system support 309
Example of configuring a project with vcpkg 309
Utilizing Docker for C++ builds 310
Summary 318
14
Version Control 321
What is a good commit? 322
The principle of singular focus 322
The art of communication 322
The art of refinement 323
Conventional Commits specification 324
Linking code to context 324
Overview and intent 325
Options and usage 325
Origins and adoption 327
Advantages of Conventional Commits 327
Commitlint – enforcing commit
message standards 327
Installation 328
Configuration 328
Local usage 328
Customizing rules 329
Basic configuration 329
Custom rule configuration 330
Scope and subject configuration 330
Customizing and sharing configurations 331
Integration with CI 331
Generating changelogs 334
Installation 334
GitCliff usage 334
Utilizing git-bisect in bug hunting 338
Summary 341
15
Code Review 343
What is a code review and why is it
needed? 343
Benefits of code reviews 344
Preparing for code reviews 345
Clear guidelines 345
Self-review 346
How to pass a code review 347
Discuss big features with reviewers and code
owners before writing code 347
Go over your code before publishing it 347
Make sure the code is compliant with the
code convention 347
Code review is a conversation, not an order 347
Remember – your code is not you 348
How to efficiently dispute during a
code review 348
Clear justification for changes 348
Reciprocal explanation from reviewees 348
Direct communication 349
Involving additional perspectives 349
How to be a good reviewer 349
Initiate the conversation 349
Maintain politeness and respect 349
Review manageable chunks 350
Avoid personal bias 350
Focus on understandability 350
Summary 351
Index 353
Other Books You May Enjoy 364
Despite the prevalence of higher-level languages, C++ is still running the world, from bare-metal embedded systems to distributed cloud-native systems. C++ is on the frontline whenever there is a need for a performance-sensitive tool supporting complex data structures. The language has been actively evolving for the last two decades.
This book is a comprehensive guide that shows you how to implement SOLID principles and refactor legacy code using the modern features and approaches of C++, the standard library, Boost library collection, and Guidelines Support Library by Microsoft. The book begins by describing the essential elements of writing clean code and discussing object-oriented programming in C++. You'll explore the design principles of software testing with examples of using popular unit testing frameworks such as Google Test. The book also guides you through applying automated tools for static and dynamic code analysis using Clang Tools.
By the end of this book, you'll be proficient in applying industry-approved coding practices to design clean, sustainable, and readable real-world C++ code.
Leverage the rich type system of C++ to write safe and elegant code
Create advanced object-oriented designs using the unique features of C++
Minimize code duplication by using metaprogramming
Refactor code safely with the help of unit tests
Ensure code conventions and format with clang-format
Facilitate the usage of modern features automatically with clang-tidy
Catch complex bugs such as memory leakage and data races with Clang AddressSanitizer and ThreadSanitizer
This book will benefit experienced C++ programmers the most, but is also suitable for technical leaders, software architects, and senior software engineers who want to save on costs and improve software development process efficiency by using modern C++ features and automated tools.