본문으로 건너뛰기

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