본문으로 건너뛰기

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