Moving to Modern C++
3.1 Distinguish between () and {} When Creating Objects
1) Initialization Syntax
Initialization:
The process of giving an object its initial value when it is created.
Parenthesized Initialization:
Initialization that uses parentheses to pass arguments to a constructor.
Widget w(10);
Braced Initialization:
Initialization that uses braces to initialize an object.
Widget w{10};
Uniform Initialization:
The use of braces as a general initialization syntax across many different contexts in C++.
2) Narrowing Conversion
Narrowing Conversion:
A conversion that may lose information because the destination type cannot represent every possible value of the source type.
Braced initialization prevents implicit narrowing conversions.
double x = 1.5;
int a{x}; // error
int b(x); // allowed
3) Most Vexing Parse
Most Vexing Parse:
A C++ parsing rule where syntax intended to create an object may instead be interpreted as a function declaration.
Widget w1(); // function declaration
Widget w2{}; // object construction
Braced initialization avoids this ambiguity.
4) std::initializer_list
std::initializer_list:
A lightweight object representing a sequence of values supplied using brace initialization.
Widget(std::initializer_list<int> values);
Initializer-list Constructor:
A constructor whose parameter is a specialization of std::initializer_list.
Initializer-list Preference:
When braces are used, constructor overload resolution strongly prefers constructors taking std::initializer_list when such a match is possible.
5) () vs {}
Parentheses:
Primarily express constructor invocation with ordinary overload resolution.
Braces:
Prevent narrowing and the most vexing parse, but may select an std::initializer_list constructor unexpectedly.
For some types, the two forms can therefore produce different objects.
std::vector<int> v1(10, 20);
std::vector<int> v2{10, 20};
v1 contains 10 elements whose value is 20.
v2 contains the two elements 10 and 20.
3.2 Prefer nullptr to 0 and NULL
1) Null Pointer
Null Pointer:
A pointer value that does not point to an object or function.
0:
An integer literal that can also act as a null pointer constant in pointer contexts.
NULL:
A legacy null pointer constant typically defined using an integral value.
Neither 0 nor NULL inherently has a pointer type.
2) nullptr
nullptr:
The C++11 null pointer literal used to represent a null pointer without using an integer value.
int* p = nullptr;
std::nullptr_t:
The type of nullptr.
nullptr can convert to compatible pointer types but does not behave like an ordinary integer.
3) Overload Resolution
Overload Resolution:
The process by which the compiler selects the best matching overloaded function.
void f(int);
void f(void*);
f(0); // f(int)
f(nullptr); // f(void*)
Using nullptr clearly expresses pointer intent and avoids ambiguity with integral overloads.
4) Template Type Deduction
nullptr also preserves pointer semantics when passed through templates.
auto result = findRecord(nullptr);
Prefer nullptr whenever a null pointer is intended.
3.3 Prefer Alias Declarations to typedefs
1) Type Alias
Type Alias:
An alternative name for an existing type.
typedef:
The traditional C++ syntax for defining a type alias.
typedef std::unique_ptr<int> UPtr;
Alias Declaration:
The modern C++ syntax for defining a type alias using using.
using UPtr = std::unique_ptr<int>;
2) Function Pointer Alias
Alias declarations are generally easier to read when defining complicated types.
using FP = void (*)(int, const std::string&);
3) Alias Template
Alias Template:
A template that creates a family of type aliases.
template<typename T>
using MyAllocList =
std::list<T, MyAlloc<T>>;
Unlike typedef, alias declarations directly support templates.
4) Dependent Type
Dependent Type:
A type whose meaning depends on a template parameter.
Traditional nested typedef patterns often require typename when accessed through dependent types.
Alias templates can simplify such declarations.
5) Type Traits
Type Trait:
A template that provides compile-time information or transformations for types.
C++11 type traits commonly expose transformed types through a nested ::type.
typename std::remove_const<T>::type
C++14 provides alias-based forms ending in _t.
std::remove_const_t<T>
3.4 Prefer Scoped Enums to Unscoped Enums
1) Unscoped Enum
Unscoped Enum:
A traditional enumeration whose enumerator names are introduced into the surrounding scope.
enum Color {
black,
white,
red
};
Enumerator names can therefore collide with other names in the same scope.
2) Scoped Enum
Scoped Enum:
An enumeration declared with enum class whose enumerators remain inside the enumeration's scope.
enum class Color {
black,
white,
red
};
Values are accessed through the enum type.
Color c = Color::red;
3) Implicit Conversion
Unscoped enums may implicitly convert to integral types.
Scoped enums do not implicitly convert to integers.
enum class Color { black, white, red };
Color c = Color::red;
// int x = c; // error
This provides stronger type safety.
4) Underlying Type
Underlying Type:
The integral type used internally to represent an enumeration.
enum class Status : std::uint8_t {
idle,
running
};
Scoped enums have a known default underlying type of int unless another type is specified.
5) Forward Declaration
Forward Declaration:
A declaration that introduces a type without providing its complete definition.
Scoped enums can be forward-declared easily because their underlying type is known.
enum class Status;
3.5 Prefer Deleted Functions to Private Undefined Ones
1) Deleted Function
Deleted Function:
A function explicitly declared as unavailable using = delete.
Widget(const Widget&) = delete;
Any attempt to use the function produces a compile-time error.
2) Preventing Copying
Copy construction and copy assignment can be disabled explicitly.
class Widget {
public:
Widget(const Widget&) = delete;
Widget& operator=(const Widget&) = delete;
};
3) Private Undefined Function
Private Undefined Function:
An older C++ technique where a function is declared private but intentionally left undefined.
Deleted functions are preferable because misuse is detected more clearly during compilation.
4) Deleted Overload
Any function can be deleted, not only special member functions.
bool isLucky(int);
bool isLucky(char) = delete;
bool isLucky(bool) = delete;
bool isLucky(double) = delete;
This can prevent unwanted implicit conversions.
5) Deleted Template Specialization
Specific template instantiations can also be prohibited by deleting corresponding overloads or specializations.
This allows APIs to reject particular types at compile time.
3.6 Declare Overriding Functions override
1) Virtual Function
Virtual Function:
A member function whose implementation can be replaced by a derived class.
2) Override
Override:
A derived-class function that replaces a virtual function inherited from a base class.
For overriding to occur, the function signatures must satisfy C++ override rules.
3) override Specifier
override:
A specifier that tells the compiler that a function is intended to override a base-class virtual function.
class Derived : public Base {
public:
void update() override;
};
If the function does not actually override a base function, compilation fails.
4) Override Mismatch
Differences involving parameter types, const, reference qualifiers, or other parts of the function declaration may prevent overriding.
override lets the compiler detect these mistakes.
5) Reference Qualifier
Reference Qualifier:
A member-function qualifier that restricts whether the function can be called on lvalue or rvalue objects.
class Widget {
public:
void process() &;
void process() &&;
};
& Reference Qualifier:
Allows the function to be called on lvalue objects.
&& Reference Qualifier:
Allows the function to be called on rvalue objects.
3.7 Prefer const_iterators to Iterators
1) Iterator
Iterator:
An object that identifies a position in a sequence and provides access to its elements.
2) const_iterator
const_iterator:
An iterator that allows reading an element but prevents modification through the iterator.
It is conceptually similar to a pointer-to-const.
Use it whenever the pointed-to element does not need to be modified.
3) cbegin and cend
cbegin:
Returns a const_iterator referring to the beginning of a container.
cend:
Returns a const_iterator referring to the end of a container.
auto it = values.cbegin();
4) Non-member Iterator Functions
std::begin:
Returns an iterator to the beginning of a container-like object.
std::end:
Returns an iterator to the end.
Generic code can prefer non-member iterator functions because they also support types such as built-in arrays.
3.8 Declare Functions noexcept If They Won't Emit Exceptions
1) noexcept
noexcept:
A function specification indicating that the function is not expected to emit exceptions.
void update() noexcept;
2) Exception Guarantee
A noexcept declaration forms part of the function's interface and communicates that exceptions will not escape the function.
If an exception escapes a noexcept function, std::terminate is invoked.
3) Optimization
Knowing that a function cannot emit exceptions may allow the compiler and Standard Library to generate more efficient code.
4) Move Operations
Move constructors and move assignment operators are especially important candidates for noexcept.
Standard Library containers may prefer copying instead of moving elements when a move operation could throw.
Widget(Widget&&) noexcept;
5) Conditional noexcept
Conditional noexcept:
A noexcept specification whose value is determined by a compile-time expression.
void swap(Widget& a, Widget& b)
noexcept(noexcept(a.swap(b)));
Use noexcept only when the function's implementation can actually satisfy the guarantee.
3.9 Use constexpr Whenever Possible
1) Constant Expression
Constant Expression:
An expression whose value can be evaluated during compilation.
2) constexpr Object
constexpr Object:
An object whose value is constant and must be initialized with a constant expression.
constexpr int size = 10;
A constexpr object is also const.
3) constexpr Function
constexpr Function:
A function that can produce a compile-time result when called with compile-time-compatible arguments.
constexpr int square(int x)
{
return x * x;
}
constexpr int value = square(5);
The same function may also be called with runtime values.
4) Compile-time Evaluation
Compile-time Evaluation:
Evaluation performed by the compiler before the program executes.
Compile-time values can be used in contexts requiring constant expressions.
5) Literal Type
Literal Type:
A type whose objects can participate in constant expressions under the language's constexpr rules.
User-defined classes can support compile-time computation when their constructors and operations satisfy the required rules.
6) constexpr Interface
Declaring an operation constexpr increases the contexts in which it can be used.
Use constexpr whenever the operation logically supports compile-time evaluation.
3.10 Make const Member Functions Thread Safe
1) const Member Function
const Member Function:
A member function that promises not to modify the observable logical state of an object.
int value() const;
2) Thread Safety
Thread Safety:
The property that code behaves correctly when accessed concurrently by multiple threads.
A const member function may be called simultaneously from multiple threads, so internal mutation can create data races.
3) Data Race
Data Race:
Concurrent access to the same memory where at least one operation writes and the accesses are not properly synchronized.
A data race results in undefined behavior.
4) mutable
mutable:
A member specifier that allows a data member to be modified inside a const member function.
mutable bool cacheValid;
mutable is commonly used for caches, mutexes, and other implementation details.
5) Mutex
Mutex:
A synchronization object that allows only one thread at a time to access a protected critical section.
mutable std::mutex m;
A mutex can protect mutable state modified inside a const member function.
6) std::atomic
std::atomic:
A type providing atomic operations that can safely participate in concurrent access without ordinary data races.
Atomic variables may be preferable to mutexes for simple independent state.
7) Logical Constness
Logical Constness:
The idea that a const member function may modify internal implementation details as long as the externally observable value of the object does not logically change.
Such internal modifications must still be thread safe.
3.11 Understand Special Member Function Generation
1) Special Member Functions
Special Member Function:
A member function that the compiler may automatically generate for a class.
The major special member functions are:
- Default constructor
- Destructor
- Copy constructor
- Copy assignment operator
- Move constructor
- Move assignment operator
2) Default Constructor
Default Constructor:
A constructor that can create an object without explicit arguments.
Widget();
The compiler may generate one when no user-declared constructor prevents its generation.
3) Destructor
Destructor:
A special member function invoked when an object's lifetime ends.
~Widget();
It is responsible for object cleanup.
4) Copy Constructor
Copy Constructor:
Creates a new object from an existing object of the same type.
Widget(const Widget&);
5) Copy Assignment Operator
Copy Assignment Operator:
Replaces the state of an existing object with a copy of another object.
Widget& operator=(const Widget&);
6) Move Constructor
Move Constructor:
Creates a new object by transferring resources from an rvalue object.
Widget(Widget&&);
7) Move Assignment Operator
Move Assignment Operator:
Transfers resources from an rvalue object into an existing object.
Widget& operator=(Widget&&);
8) Move Operation Generation
Move Operations:
The move constructor and move assignment operator.
The compiler generates move operations only when the class satisfies the required generation rules.
Declaring certain copy operations, move operations, or a destructor can suppress automatic move generation.
9) Copy Operation Interaction
Declaring a move operation can cause implicitly generated copy operations to become unavailable.
Copy and move operations therefore should be considered together when designing a class.
10) = default
Defaulted Function:
A special member function explicitly requested to use the compiler-generated implementation.
Widget(const Widget&) = default;
= default makes the programmer's intent explicit while retaining compiler-generated behavior.
11) = delete
Deleted Special Member Function:
A special member function explicitly disabled using = delete.
Widget(const Widget&) = delete;
This prevents the corresponding operation from being used.
12) Rule of Zero
Rule of Zero:
A design principle where classes avoid manually defining special member functions by delegating resource management to types that already manage their own resources.
Standard containers and smart pointers make this approach practical.
13) Rule of Five
Rule of Five:
A guideline stating that a class requiring custom resource-management behavior for one copy, move, or destruction operation should consider whether all five related operations need explicit treatment.
The five operations are:
- Destructor
- Copy constructor
- Copy assignment operator
- Move constructor
- Move assignment operator