Exceptional C++: A Book by Herb Sutter with 58 Tips and Tricks for C++ Programmers
Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions by Herb Sutter
If you are a C++ programmer who wants to improve your skills, learn from the experts, and solve challenging problems, then this book is for you. Exceptional C++ is a collection of 47 articles that cover various aspects of C++ programming, such as generic programming, exception safety, class design, inheritance, templates, and more. Each article presents a problem or a puzzle, followed by a detailed analysis and solution. The book is written by Herb Sutter, one of the most influential C++ experts in the world. In this article, I will give you an overview of the book, its author, and its main topics.
exceptional c herb sutter pdf 58
Introduction
What is Exceptional C++?
Exceptional C++ is not a typical C++ book that teaches you the syntax and features of the language. Rather, it is a book that challenges you to think deeply about how to use C++ effectively and correctly. The book assumes that you already have some experience with C++, but it does not require you to be an expert. The book covers topics that are relevant for both beginners and advanced programmers, such as:
How to write generic code using templates and the standard library
How to handle errors and exceptions in a robust and safe way
How to design classes and interfaces that are easy to use and extend
How to avoid common pitfalls and traps that can lead to bugs and inefficiencies
How to apply best practices and idioms that are widely accepted by the C++ community
The book is divided into three parts, each containing several articles that focus on a specific theme. Each article consists of four sections:
A problem statement that poses a question or a puzzle about some aspect of C++ programming
A hint section that gives you some clues or suggestions on how to approach the problem
A solution section that explains the correct answer and the reasoning behind it
A discussion section that explores alternative solutions, trade-offs, implications, and extensions of the problem
The book is designed to be read in any order, depending on your interests and needs. You can read each article independently, or you can follow the suggested reading order that groups related articles together. You can also use the book as a reference or a source of inspiration for your own projects.
Who is Herb Sutter?
Herb Sutter is one of the most respected and influential C++ experts in the world. He is the author of several books, including C++ Coding Standards, More Exceptional C++, and C++ Concurrency in Action. He is also a prolific writer of articles, columns, and blogs on various C++ topics. He is the chair of the ISO C++ standards committee, where he has been involved in the design and evolution of the language since 1997. He is also a software architect at Microsoft, where he leads the C++ team. He has been awarded the Dr. Dobb's Excellence in Programming Award and the Software Development Magazine Jolt Award for his contributions to the C++ community.
Why read this book?
This book is a valuable resource for any C++ programmer who wants to learn from the best and improve their skills. By reading this book, you will:
Gain a deeper understanding of how C++ works under the hood
Learn how to write code that is clear, correct, efficient, and portable
Avoid common mistakes and misconceptions that can lead to bugs and bad performance
Discover new techniques and features that can make your code more expressive and powerful
Challenge yourself with interesting and realistic problems that test your knowledge and creativity
Have fun and enjoy the beauty and complexity of C++
Main Content
Part I: Generic Programming and the C++ Standard Library
The first part of the book covers topics related to generic programming and the C++ standard library. Generic programming is a paradigm that allows you to write code that can work with different types of data, without having to write separate versions for each type. The C++ standard library provides a set of generic containers, algorithms, iterators, and functions that can help you write generic code more easily and efficiently.
In this part, you will learn how to:
Distinguish between pointers and references, and when to use each one
Prefer const and inline to #define, and why macros are evil
Avoid using new and delete directly, and use smart pointers instead
Use vector and string instead of arrays, and why arrays are evil
Use template functions for generic algorithms, and how to write your own templates
Item 1: Distinguish between pointers and references
A pointer is a variable that holds the address of another variable. A reference is an alias for another variable. Both pointers and references allow you to access or modify the value of another variable indirectly. However, they have some important differences that you need to be aware of:
PointersReferences
Can be null or point to nothingCannot be null or refer to nothing
Can be reassigned to point to different variablesCannot be reassigned to refer to different variables
Need to be dereferenced with * or -> to access or modify the value they point toDo not need to be dereferenced; they act as if they were the variable they refer to
Can point to or refer to const or non-const variables, depending on whether they are declared as const or non-const pointersCan only refer to const variables if they are declared as const references; otherwise, they can only refer to non-const variables
Can be used as parameters for functions that need to modify their arguments or return multiple values; however, they need to be checked for null before using themCan also be used as parameters for functions that need to modify their arguments or return multiple values; however, they do not need to be checked for null because they are guaranteed to be valid
```html they should not be used to return a reference to a local variable that goes out of scope when the function returns
As a general rule, you should prefer references over pointers whenever possible, because they are safer and easier to use. However, there are some situations where pointers are necessary or more convenient, such as:
When you need to represent the absence of a value or an optional value
When you need to perform pointer arithmetic or access memory directly
When you need to work with dynamic arrays or polymorphic objects
When you need to implement data structures that use pointers internally, such as linked lists or trees
Item 2: Prefer const and inline to #define
In C++, you can use the #define directive to create macros that replace a name with a value or an expression. For example:
#define PI 3.14 #define SQUARE(x) ((x) * (x))
However, using macros has several drawbacks and risks, such as:
Macros are not subject to scope rules or type checking; they can cause name collisions or type errors that are hard to debug
Macros can have unintended side effects or unexpected results; for example, SQUARE(++x) will increment x twice instead of once
Macros can make the code less readable and maintainable; they can hide the actual meaning or logic of the code
Macros can interfere with the debugging process; they can prevent the debugger from showing the correct values or locations of variables or expressions
Therefore, you should avoid using macros whenever possible, and use const and inline instead. Const and inline are keywords that allow you to define constants and functions that have similar benefits as macros, but without the drawbacks. For example:
const double PI = 3.14; inline double square(double x) return x * x;
By using const and inline, you can:
Ensure that your constants and functions are scoped and typed correctly; they can avoid name collisions or type errors
Ensure that your constants and functions have no side effects or unexpected results; they can behave as expected
Improve the readability and maintainability of your code; they can make the code more clear and consistent
Facilitate the debugging process; they can allow the debugger to show the correct values or locations of variables or expressions
The only exception where you might still need to use #define is when you need to create conditional compilation directives, such as #ifdef or #ifndef. These directives allow you to include or exclude parts of your code depending on certain conditions, such as the platform or the compiler. However, you should use them sparingly and carefully, because they can also make your code less portable and more complex.
Item 3: Avoid using new and delete directly
In C++, you can use the new and delete operators to allocate and deallocate memory dynamically. For example:
int* p = new int(42); // allocate an int on the heap and initialize it to 42 delete p; // deallocate the int from the heap
However, using new and delete directly has several disadvantages and dangers, such as:
You have to remember to delete every object that you allocate with new; otherwise, you will cause memory leaks that waste resources and degrade performance
You have to make sure that you delete every object exactly once; otherwise, you will cause undefined behavior that can corrupt memory and crash your program
You have to handle exceptions properly; otherwise, you will cause memory leaks or undefined behavior if an exception is thrown before you delete an object
You have to deal with low-level details of memory management; otherwise, you will make your code more complicated and error-prone
Therefore, you should avoid using new and delete directly whenever possible, and use smart pointers instead. Smart pointers are classes that wrap a raw pointer and manage its lifetime automatically. They use a technique called RAII (Resource Acquisition Is Initialization) to ensure that the object pointed by the smart pointer is deleted when the smart pointer goes out of scope or is reassigned. For example:
#include
std::unique_ptr p(new int(42)); // allocate an int on the heap and initialize it to 42 // no need to delete p; it will be deleted automatically when p goes out of scope or is reassigned
By using smart pointers, you can:
Prevent memory leaks and undefined behavior; the smart pointer will delete the object for you
Simplify exception handling; the smart pointer will delete the object for you even if an exception is thrown
Abstract away the low-level details of memory management; the smart pointer will handle them for you
The C++ standard library provides several types of smart pointers, such as std::unique_ptr, std::shared_ptr, and std::weak_ptr. Each type has its own semantics and use cases, depending on how you want to share or transfer ownership of the object. You should choose the appropriate type of smart pointer for your situation, and follow the guidelines and best practices for using them correctly.
Item 4: Use vector and string instead of arrays
In C++, you can use arrays to store a sequence of elements of the same type. For example:
int a[10]; // declare an array of 10 ints a[0] = 1; // assign 1 to the first element a[9] = 10; // assign 10 to the last element
However, using arrays has several limitations and risks, such as:
You have to specify the size of the array at compile time; you cannot resize or expand the array at run time
You have to pass the size of the array along with the array when you pass it to a function; otherwise, the function will not know how many elements are in the array
You have to be careful with array decay; when you pass an array to a function, it decays into a pointer to its first element, losing its size information
You have to avoid buffer overflow; when you access an element of an array, you have to make sure that the index is within the bounds of the array, otherwise you will access invalid memory
You have to manage the lifetime of an array; if you allocate an array on the heap with new[], you have to deallocate it with delete[]; if you allocate an array on the stack, it will be destroyed when it goes out of scope
Therefore, you should avoid using arrays whenever possible, and use vector and string instead. Vector and string are classes that represent dynamic arrays that can grow and shrink as needed. They are part of the C++ standard library, and they provide many features and benefits that arrays do not. For example:
#include
#include
std::vector v; // declare a vector of ints v.push_back(1); // append 1 to the end of the vector v.push_back(10); // append 10 to the end of the vector std::string s; // declare a string s += "Hello"; // append "Hello" to the end of the string s += " world"; // append " world" to the end of the string
By using vector and string, you can:
Resize or expand your sequence as needed at run time; vector and string will allocate or deallocate memory for you as necessary
Pass your sequence to a function without passing its size; vector and string have member functions that can tell you how many elements are in them
Avoid array decay; when you pass a vector or a string to a function, it will not decay into a pointer; it will retain its type and size information
Avoid buffer overflow; vector and string have bounds checking mechanisms that can prevent you from accessing invalid memory; they also have iterators that can help you traverse them safely
Avoid managing the lifetime of your sequence; vector and string use RAII to manage their memory automatically; they will be destroyed when they go out of scope or are reassigned
In addition, vector and string also provide many other features and benefits that arrays do not, such as:
They can store any type of elements, not just primitive types; they can store user-defined types, such as classes or structs
They can work with generic algorithms from the standard library, such as sort or find; they can also provide their own algorithms, such as erase or replace
They can support different operations and interfaces, such as insertion, deletion, concatenation, comparison, or conversion
```html They can support different operations and interfaces, such as insertion, deletion, concatenation, comparison, or conversion
They can be initialized or assigned with different syntaxes, such as initializer lists, range constructors, or copy constructors
They can be compatible with C-style arrays or strings, such as using data() or c_str() to get a pointer to their underlying array
The only exception where you might still need to use arrays is when you need to interface with legacy code or libraries that expect C-style arrays or strings. In that case, you can use vector or string to create and manipulate your sequence, and then convert it to an array or a string when you need to pass it to the external code or library.
Item 5: Use template functions for generic algorithms
In C++, you can use template functions to write generic algorithms that can work with different types of data. A template function is a function that takes one or more template parameters that represent the types of the data. The template parameters are enclosed in angle brackets () after the function name. For example:
template
T max(T x, T y) return x > y ? x : y;
This is a template function that takes two arguments of type T and returns the maximum of them. The type T is a template parameter that can be any type that supports the > operator. For example:
int a = 10; int b = 20; int c = max(a, b); // c is 20; T is int double d = 3.14; double e = 2.71; double f = max(d, e); // f is 3.14; T is double std::string g = "Hello"; std::string h = "World"; std::string i = max(g, h); // i is "World"; T is std::string
By using template functions, you can:
Write generic code that can work with different types of data, without having to write separate versions for each type
Avoid code duplication and inconsistency; you only need to write and maintain one version of the algorithm
Improve performance and efficiency; the compiler will generate a specialized version of the algorithm for each type at compile time
Enhance readability and maintainability; you only need to understand and document one version of the algorithm
The C++ standard library provides many template functions that implement common and useful algorithms, such as sort, find, count, accumulate, transform, and more. You can use these template functions to perform various operations on your data, such as sorting, searching, counting, summing, modifying, and more. You can also write your own template functions to implement your own algorithms.
When writing template functions, you should follow some guidelines and best practices, such as:
Use typename instead of class for template parameters; they are equivalent in meaning, but typename is more clear and consistent
Use descriptive names for template parameters; avoid using single letters or generic names like T or U; use names that reflect the role or concept of the parameter
Use const references for parameters that are not modified by the algorithm; this can avoid unnecessary copying and allow the algorithm to work with non-copyable types
Use iterator parameters for sequences of elements; this can make the algorithm more generic and flexible; it can work with any type of container that supports iterators
Use function objects or lambdas for predicates or actions; this can make the algorithm more customizable and expressive; it can accept any type of callable object that matches the expected signature
Use enable_if or concepts (in C++20) for constraints or requirements; this can make the algorithm more robust and safe; it can check if the types passed to the algorithm satisfy certain conditions or support certain operations
Part II: Exception Safety Issues and Techniques
The second part of the book covers topics related to exception safety and techniques. Exception safety is a property of code that guarantees that it will not cause any unwanted effects or leaks when an exception is thrown. An exception is an event that occurs when something goes wrong during the execution of a program. C++ provides a mechanism for throwing and catching exceptions using the keywords throw and catch. For example:
void foo() throw std::runtime_error("Something went wrong in foo"); void bar() try foo(); catch (const std::exception& e) std::cout
This is an example of a function that throws an exception of type std::runtime_error, and a function that catches the exception and handles it. When an exception is thrown, the program will unwind the stack and look for a matching catch block that can