Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Modern C++ Programming Cookbook
Modern C++ Programming Cookbook

Modern C++ Programming Cookbook: Recipes to explore data structure, multithreading, and networking in C++17

eBook
€28.99 €32.99
Paperback
€41.99
Subscription
Free Trial
Renews at €18.99p/m

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Table of content icon View table of contents Preview book icon Preview Book

Modern C++ Programming Cookbook

Learning Modern Core Language Features

The recipes included in this chapter are as follows:

  • Using auto whenever possible
  • Creating type aliases and alias templates
  • Understanding uniform initialization
  • Understanding the various forms of non-static member initialization
  • Controlling and querying object alignment
  • Using scoped enumerations
  • Using override and final for virtual methods
  • Using range-based for loops to iterate on a range
  • Enabling range-based for loops for custom types
  • Using explicit constructors and conversion operators to avoid implicit conversion
  • Using unnamed namespaces instead of static globals
  • Using inline namespaces for symbol versioning
  • Using structured bindings to handle multi-return values

Introduction

The C++ language has gone through a major transformation in the past decade with the development and release of C++11 and then later with its newer versions C++14 and C++17. These new standards have introduced new concepts, simplified or extended existing syntax and semantics, and overall transformed the way we write code. C++11 looks like a new language, and code written using the new standards is called modern C++ code.

Using auto whenever possible

Automatic type deduction is one of the most important and widely used features in modern C++. The new C++ standards have made it possible to use auto as a placeholder for types in various contexts and let the compiler deduce the actual type. In C++11, auto can be used for declaring local variables and for the return type of a function with a trailing return type. In C++14, auto can be used for the return type of a function without specifying a trailing type and for parameter declarations in lambda expressions. Future standard versions are likely to expand the use of auto to even more cases. The use of auto in these contexts has several important benefits. Developers should be aware of them, and prefer auto whenever possible. An actual term was coined for this by Andrei Alexandrescu and promoted by Herb Sutter--almost always auto (AAA).

How to do it...

Consider using auto as a placeholder for the actual type in the following situations:

  • To declare local variables with the form auto name = expression when you do not want to commit to a specific type:
        auto i = 42;          // int 
auto d = 42.5; // double
auto s = "text"; // char const *
auto v = { 1, 2, 3 }; // std::initializer_list<int>
  • To declare local variables with the auto name = type-id { expression } form when you need to commit to a specific type:
        auto b  = new char[10]{ 0 };            // char* 
auto s1 = std::string {"text"}; // std::string
auto v1 = std::vector<int> { 1, 2, 3 }; // std::vector<int>
auto p = std::make_shared<int>(42); // std::shared_ptr<int>
  • To declare named lambda functions, with the form auto name = lambda-expression, unless the lambda needs to be passed or return to a function:
        auto upper = [](char const c) {return toupper(c); };
  • To declare lambda parameters and return values:
        auto add = [](auto const a, auto const b) {return a + b;};
  • To declare function return type when you don't want to commit to a specific type:
        template <typename F, typename T> 
auto apply(F&& f, T value)
{
return f(value);
}

How it works...

The auto specifier is basically a placeholder for an actual type. When using auto, the compiler deduces the actual type from the following instances:

  • From the type of the expression used to initialize a variable, when auto is used to declare variables.
  • From the trailing return type or the type of the return expression of a function, when auto is used as a placeholder for the return type of a function.

In some cases, it is necessary to commit to a specific type. For instance, in the preceding example, the compiler deduces the type of s to be char const *. If the intention was to have a std::string, then the type must be specified explicitly. Similarly, the type of v was deduced as std::initializer_list<int>. However, the intention could be to have a std::vector<int>. In such cases, the type must be specified explicitly on the right side of the assignment.

There are some important benefits of using the auto specifier instead of actual types; the following is a list of, perhaps, the most important ones:

  • It is not possible to leave a variable uninitialized. This is a common mistake that developers do when declaring variables specifying the actual type, but it is not possible with auto that requires an initialization of the variable in order to deduce the type.
  • Using auto ensures that you always use the correct type and that implicit conversion will not occur. Consider the following example where we retrieve the size of a vector to a local variable. In the first case, the type of the variable is int, though the size() method returns size_t. That means an implicit conversion from size_t to int will occur. However, using auto for the type will deduce the correct type, that is, size_t:
        auto v = std::vector<int>{ 1, 2, 3 }; 
int size1 = v.size();
// implicit conversion, possible loss of data
auto size2 = v.size();
auto size3 = int{ v.size() }; // error, narrowing conversion
  • Using auto promotes good object-oriented practices, such as preferring interfaces over implementations. The lesser the number of types specified the more generic the code is and more open to future changes, which is a fundamental principle of object-oriented programming.
  • It means less typing and less concern for actual types that we don't care about anyways. It is very often that even though we explicitly specify the type, we don't actually care about it. A very common case is with iterators, but one can think of many more. When you want to iterate over a range, you don't care about the actual type of the iterator. You are only interested in the iterator itself; so, using auto saves time used for typing possibly long names and helps you focus on actual code and not type names. In the following example, in the first for loop, we explicitly use the type of the iterator. It is a lot of text to type, the long statements can actually make the code less readable, and you also need to know the type name that you actually don't care about. The second loop with the auto specifier looks simpler and saves you from typing and caring about actual types.
        std::map<int, std::string> m; 
for (std::map<int,std::string>::const_iterator it = m.cbegin();
it != m.cend(); ++it)
{ /*...*/ }

for (auto it = m.cbegin(); it != m.cend(); ++it)
{ /*...*/ }
  • Declaring variables with auto provides a consistent coding style with the type always in the right-hand side. If you allocate objects dynamically, you need to write the type both on the left and right side of the assignment, for example, int p = new int(42). With auto, the type is specified only once on the right side.

However, there are some gotchas when using auto:

  • The auto specifier is only a placeholder for the type, not for the const/volatile and references specifiers. If you need a const/volatile and/or reference type, then you need to specify them explicitly. In the following example, foo.get() returns a reference to int; when variable x is initialized from the return value, the type deduced by the compiler is int, and not int&. Therefore, any change to x will not propagate to foo.x_. In order to do so, one should use auto&:
        class foo { 
int x_;
public:
foo(int const x = 0) :x_{ x } {}
int& get() { return x_; }
};

foo f(42);
auto x = f.get();
x = 100;
std::cout << f.get() << std::endl; // prints 42
  • It is not possible to use auto for types that are not moveable:
        auto ai = std::atomic<int>(42); // error
  • It is not possible to use auto for multi-word types, such as long long, long double, or struct foo. However, in the first case, the possible workarounds are to use literals or type aliases; as for the second, using struct/class in that form is only supported in C++ for C compatibility and should be avoided anyways:
        auto l1 = long long{ 42 }; // error 
auto l2 = llong{ 42 }; // OK
auto l3 = 42LL; // OK
  • If you use the auto specifier but still need to know the type, you can do so in any IDE by putting the cursor over a variable, for instance. If you leave the IDE, however, that is not possible anymore, and the only way to know the actual type is to deduce it yourself from the initialization expression, which could probably mean searching through the code for function return types.

The auto can be used to specify the return type from a function. In C++11, this requires a trailing return type in the function declaration. In C++14, this has been relaxed, and the type of the return value is deduced by the compiler from the return expression. If there are multiple return values they should have the same type:

    // C++11 
auto func1(int const i) -> int
{ return 2*i; }

// C++14
auto func2(int const i)
{ return 2*i; }

As mentioned earlier, auto does not retain const/volatile and reference qualifiers. This leads to problems with auto as a placeholder for the return type from a function. To explain this, let us consider the preceding example with foo.get(). This time we have a wrapper function called proxy_get() that takes a reference to a foo, calls get(), and returns the value returned by get(), which is an int&. However, the compiler will deduce the return type of proxy_get() as being int, not int&. Trying to assign that value to an int& fails with an error:

    class foo 
{
int x_;
public:
foo(int const x = 0) :x_{ x } {}
int& get() { return x_; }
};

auto proxy_get(foo& f) { return f.get(); }

auto f = foo{ 42 };
auto& x = proxy_get(f); // cannot convert from 'int' to 'int &'

To fix this, we need to actually return auto&. However, this is a problem with templates and perfect forwarding the return type without knowing whether that is a value or a reference. The solution to this problem in C++14 is decltype(auto) that will correctly deduce the type:

    decltype(auto) proxy_get(foo& f) { return f.get(); } 
auto f = foo{ 42 };
decltype(auto) x = proxy_get(f);

The last important case where auto can be used is with lambdas. As of C++14, both lambda return type and lambda parameter types can be auto. Such a lambda is called a generic lambda because the closure type defined by the lambda has a templated call operator. The following shows a generic lambda that takes two auto parameters and returns the result of applying operator+ on the actual types:

    auto ladd = [] (auto const a, auto const b) { return a + b; }; 
struct
{
template<typename T, typename U>
auto operator () (T const a, U const b) const { return a+b; }
} L;

This lambda can be used to add anything for which the operator+ is defined. In the following example, we use the lambda to add two integers and to concatenate to std::string objects (using the C++14 user-defined literal operator ""s):

    auto i = ladd(40, 2);            // 42 
auto s = ladd("forty"s, "two"s); // "fortytwo"s

See also

  • Creating type aliases and alias templates
  • Understanding uniform initialization

Creating type aliases and alias templates

In C++, it is possible to create synonyms that can be used instead of a type name. This is achieved by creating a typedef declaration. This is useful in several cases, such as creating shorter or more meaningful names for a type or names for function pointers. However, typedef declarations cannot be used with templates to create template type aliases. An std::vector<T>, for instance, is not a type (std::vector<int> is a type), but a sort of family of all types that can be created when the type placeholder T is replaced with an actual type.

In C++11, a type alias is a name for another already declared type, and an alias template is a name for another already declared template. Both of these types of aliases are introduced with a new using syntax.

How to do it...

  • Create type aliases with the form using identifier = type-id as in the following examples:
        using byte    = unsigned char; 
using pbyte = unsigned char *;
using array_t = int[10];
using fn = void(byte, double);

void func(byte b, double d) { /*...*/ }

byte b {42};
pbyte pb = new byte[10] {0};
array_t a{0,1,2,3,4,5,6,7,8,9};
fn* f = func;
  • Create alias templates with the form template<template-params-list> identifier = type-id as in the following examples:
        template <class T> 
class custom_allocator { /* ... */};

template <typename T>
using vec_t = std::vector<T, custom_allocator<T>>;

vec_t<int> vi;
vec_t<std::string> vs;

For consistency and readability, you should do the following:

  • Not mix typedef and using declarations for creating aliases.
  • Use the using syntax to create names of function pointer types.

How it works...

A typedef declaration introduces a synonym (or an alias in other words) for a type. It does not introduce another type (like a class, struct, union, or enum declaration). Type names introduced with a typedef declaration follow the same hiding rules as identifier names. They can also be redeclared, but only to refer to the same type (therefore, you can have valid multiple typedef declarations that introduce the same type name synonym in a translation unit as long as it is a synonym for the same type). The following are typical examples of typedef declarations:

    typedef unsigned char   byte; 
typedef unsigned char * pbyte;
typedef int array_t[10];
typedef void(*fn)(byte, double);

template<typename T>
class foo {
typedef T value_type;
};

typedef std::vector<int> vint_t;

A type alias declaration is equivalent to a typedef declaration. It can appear in a block scope, class scope, or namespace scope. According to C++11 paragraph 7.1.3.2:

A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. It has the same semantics as if it were introduced by the typedef specifier. In particular, it does not define a new type and it shall not appear in the type-id.

An alias-declaration is, however, more readable and more clear about the actual type that is aliased when it comes to creating aliases for array types and function pointer types. In the examples from the How to do it... section, it is easily understandable that array_t is a name for the type array of 10 integers, and fn is a name for a function type that takes two parameters of type byte and double and returns void. That is also consistent with the syntax for declaring std::function objects (for example, std::function<void(byte, double)> f).

The driving purpose of the new syntax is to define alias templates. These are templates which, when specialized, are equivalent to the result of substituting the template arguments of the alias template for the template parameters in the type-id.

It is important to take note of the following things:

  • Alias templates cannot be partially or explicitly specialized.
  • Alias templates are never deduced by template argument deduction when deducing a template parameter.
  • The type produced when specializing an alias template is not allowed to directly or indirectly make use of its own type.

Understanding uniform initialization

Brace-initialization is a uniform method for initializing data in C++11. For this reason, it is also called uniform initialization. It is arguably one of the most important features from C++11 that developers should understand and use. It removes previous distinctions between initializing fundamental types, aggregate and non-aggregate types, and arrays and standard containers.

Getting ready

For continuing with this recipe, you need to be familiar with direct initialization that initializes an object from an explicit set of constructor arguments and copy initialization that initializes an object from another object. The following is a simple example of both types of initialization, but for further details, you should see additional resources:

    std::string s1("test");   // direct initialization 
std::string s2 = "test"; // copy initialization

How to do it...

To uniformly initialize objects regardless of their type, use the brace-initialization form {} that can be used for both direct initialization and copy initialization. When used with brace initialization, these are called direct list and copy list initialization.

    T object {other};   // direct list initialization 
T object = {other}; // copy list initialization

Examples of uniform initialization are as follows:

  • Standard containers:
        std::vector<int> v { 1, 2, 3 };
std::map<int, std::string> m { {1, "one"}, { 2, "two" }};
  • Dynamically allocated arrays:
        int* arr2 = new int[3]{ 1, 2, 3 };    
  • Arrays:
        int arr1[3] { 1, 2, 3 }; 
  • Built-in types:
        int i { 42 };
double d { 1.2 };
  • User-defined types:
        class foo
{
int a_;
double b_;
public:
foo():a_(0), b_(0) {}
foo(int a, double b = 0.0):a_(a), b_(b) {}
};

foo f1{};
foo f2{ 42, 1.2 };
foo f3{ 42 };
  • User-defined POD types:
        struct bar { int a_; double b_;};
bar b{ 42, 1.2 };

How it works...

Before C++11 objects required different types of initialization based on their type:

  • Fundamental types could be initialized using assignment:
        int a = 42; 
double b = 1.2;
  • Class objects could also be initialized using assignment from a single value if they had a conversion constructor (prior to C++11, a constructor with a single parameter was called a conversion constructor):
        class foo 
{
int a_;
public:
foo(int a):a_(a) {}
};
foo f1 = 42;
  • Non-aggregate classes could be initialized with parentheses (the functional form) when arguments were provided and only without any parentheses when default initialization was performed (call to the default constructor). In the next example, foo is the structure defined in the How to do it... section:
        foo f1;           // default initialization 
foo f2(42, 1.2);
foo f3(42);
foo f4(); // function declaration
  • Aggregate and POD types could be initialized with brace-initialization. In the next example, bar is the structure defined in the How to do it... section:
        bar b = {42, 1.2}; 
int a[] = {1, 2, 3, 4, 5};

Apart from the different methods of initializing the data, there are also some limitations. For instance, the only way to initialize a standard container was to first declare an object and then insert elements into it; vector was an exception because it is possible to assign values from an array that can be prior initialized using aggregate initialization. On the other hand, however, dynamically allocated aggregates could not be initialized directly.

All the examples in the How to do it... section use direct initialization, but copy initialization is also possible with brace-initialization. The two forms, direct and copy initialization, may be equivalent in most cases, but copy initialization is less permissive because it does not consider explicit constructors in its implicit conversion sequence that must produce an object directly from the initializer, whereas direct initialization expects an implicit conversion from the initializer to an argument of the constructor. Dynamically allocated arrays can only be initialized using direct initialization.

Of the classes shown in the preceding examples, foo is the one class that has both a default constructor and a constructor with parameters. To use the default constructor to perform default initialization, we need to use empty braces, that is, {}. To use the constructor with parameters, we need to provide the values for all the arguments in braces {}. Unlike non-aggregate types where default initialization means invoking the default constructor, for aggregate types, default initialization means initializing with zeros.

Initialization of standard containers, such as the vector and the map also shown above, is possible because all standard containers have an additional constructor in C++11 that takes an argument of type std::initializer_list<T>. This is basically a lightweight proxy over an array of elements of type T const. These constructors then initialize the internal data from the values in the initializer list.

The way the initialization using std::initializer_list works is the following:

  • The compiler resolves the types of the elements in the initialization list (all elements must have the same type).
  • The compiler creates an array with the elements in the initializer list.
  • The compiler creates an std::initializer_list<T> object to wrap the previously created array.
  • The std::initializer_list<T> object is passed as an argument to the constructor.

An initializer list always takes precedence over other constructors where brace-initialization is used. If such a constructor exists for a class, it will be called when brace-initialization is performed:

    class foo 
{
int a_;
int b_;
public:
foo() :a_(0), b_(0) {}

foo(int a, int b = 0) :a_(a), b_(b) {}
foo(std::initializer_list<int> l) {}
};

foo f{ 1, 2 }; // calls constructor with initializer_list<int>

The precedence rule applies to any function, not just constructors. In the following example, two overloads of the same function exist. Calling the function with an initializer list resolves to a call to the overload with an std::initializer_list:

    void func(int const a, int const b, int const c) 
{
std::cout << a << b << c << std::endl;
}

void func(std::initializer_list<int> const l)
{
for (auto const & e : l)
std::cout << e << std::endl;
}

func({ 1,2,3 }); // calls second overload

This, however, has the potential of leading to bugs. Let's take, for example, the vector type. Among the constructors of the vector, there is one that has a single argument representing the initial number of elements to be allocated and another one that has an std::initializer_list as an argument. If the intention is to create a vector with a preallocated size, using the brace-initialization will not work, as the constructor with the std::initializer_list will be the best overload to be called:

    std::vector<int> v {5};

The preceding code does not create a vector with five elements, but a vector with one element with a value 5. To be able to actually create a vector with five elements, initialization with the parentheses form must be used:

    std::vector<int> v (5);

Another thing to note is that brace-initialization does not allow narrowing conversion. According to the C++ standard (refer to paragraph 8.5.4 of the standard), a narrowing conversion is an implicit conversion:

- From a floating-point type to an integer type
- From long double to double or float, or from double to float, except where the source is a constant expression and the actual value after conversion is within the range of values that can be represented (even if it cannot be represented exactly)
- From an integer type or unscoped enumeration type to a floating-point type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted to its original type
- From an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted to its original type.

The following declarations trigger compiler errors because they require a narrowing conversion:

    int i{ 1.2 };           // error 

double d = 47 / 13;
float f1{ d }; // error
float f2{47/13}; // OK

To fix the error, an explicit conversion must be done:

    int i{ static_cast<int>(1.2) }; 

double d = 47 / 13;
float f1{ static_cast<float>(d) };
A brace-initialization list is not an expression and does not have a type. Therefore, decltype cannot be used on a brace-init list, and template type deduction cannot deduce the type that matches a brace-init list.

There's more

The following sample shows several examples of direct-list-initialization and copy-list-initialization. In C++11, the deduced type of all these expressions is std::initializer_list<int>.

auto a = {42};   // std::initializer_list<int>
auto b {42}; // std::initializer_list<int>
auto c = {4, 2}; // std::initializer_list<int>
auto d {4, 2}; // std::initializer_list<int>

C++17 has changed the rules for list initialization, differentiating between the direct- and copy-list-initialization. The new rules for type deduction are as follows:

  • for copy list initialization auto deduction will deduce a std::initializer_list<T> if all elements in the list have the same type, or be ill-formed.
  • for direct list initialization auto deduction will deduce a T if the list has a single element, or be ill-formed if there is more than one element.

Base on the new rules, the previous examples would change as follows: a and c are deduced as std::initializer_list<int>; b is deduced as an int; d, which uses direct initialization and has more than one value in the brace-init-list, triggers a compiler error.

auto a = {42};   // std::initializer_list<int>
auto b {42}; // int
auto c = {4, 2}; // std::initializer_list<int>
auto d {4, 2}; // error, too many

See also

  • Using auto whenever possible
  • Understanding the various forms of non-static member initialization

Understanding the various forms of non-static member initialization

Constructors are a place where non-static class member initialization is done. Many developers prefer assignments in the constructor body. Aside from the several exceptional cases when that is actually necessary, initialization of non-static members should be done in the constructor's initializer list or, as of C++11, using default member initialization when they are declared in the class. Prior to C++11, constants and non-constant non-static data members of a class had to be initialized in the constructor. Initialization on declaration in a class was only possible for static constants. As we will see further, this limitation was removed in C++11 that allows initialization of non-statics in the class declaration. This initialization is called default member initialization and is explained in the next sections.

This recipe will explore the ways the non-static member initialization should be done.

How to do it...

To initialize non-static members of a class you should:

  • Use default member initialization for providing default values for members of classes with multiple constructors that would use a common initializer for those members (see [3] and [4] in the following code).
  • Use default member initialization for constants, both static and non-static (see [1] and [2] in the following code).
  • Use the constructor initializer list to initialize members that don't have default values, but depend on constructor parameters (see [5] and [6] in the following code).
  • Use assignment in constructors when the other options are not possible (examples include initializing data members with pointer this, checking constructor parameter values, and throwing exceptions prior to initializing members with those values or self-references of two non-static data members).

The following example shows these forms of initialization:

    struct Control 
{
const int DefaultHeigh = 14; // [1]
const int DefaultWidth = 80; // [2]

TextVAligment valign = TextVAligment::Middle; // [3]
TextHAligment halign = TextHAligment::Left; // [4]

std::string text;

Control(std::string const & t) : text(t) // [5]
{}

Control(std::string const & t,
TextVerticalAligment const va,
TextHorizontalAligment const ha):
text(t), valign(va), halign(ha) // [6]
{}
};

How it works...

Non-static data members are supposed to be initialized in the constructor's initializer list as shown in the following example:

    struct Point 
{
double X, Y;
Point(double const x = 0.0, double const y = 0.0) : X(x), Y(y) {}
};

Many developers, however, do not use the initializer list, but prefer assignments in the constructor's body, or even mix assignments and the initializer list. That could be for several reasons--for larger classes with many members, the constructor assignments may look easier to read than long initializer lists, perhaps split on many lines, or it could be because they are familiar with other programming languages that don't have an initializer list or because, unfortunately, for various reasons they don't even know about it.

It is important to note that the order in which non-static data members are initialized is the order in which they were declared in the class definition, and not the order of their initialization in a constructor initializer list. On the other hand, the order in which non-static data members are destroyed is the reversed order of construction.

Using assignments in the constructor is not efficient, as this can create temporary objects that are later discarded. If not initialized in the initializer list, non-static members are initialized via their default constructor and then, when assigned a value in the constructor's body, the assignment operator is invoked. This can lead to inefficient work if the default constructor allocates a resource (such as memory or a file) and that has to be deallocated and reallocated in the assignment operator:

    struct foo 
{
foo()
{ std::cout << "default constructor" << std::endl; }
foo(std::string const & text)
{ std::cout << "constructor '" << text << "'" << std::endl; }
foo(foo const & other)
{ std::cout << "copy constructor" << std::endl; }
foo(foo&& other)
{ std::cout << "move constructor" << std::endl; };
foo& operator=(foo const & other)
{ std::cout << "assignment" << std::endl; return *this; }
foo& operator=(foo&& other)
{ std::cout << "move assignment" << std::endl; return *this;}
~foo()
{ std::cout << "destructor" << std::endl; }
};

struct bar
{
foo f;

bar(foo const & value)
{
f = value;
}
};

foo f;
bar b(f);

The preceding code produces the following output showing how data member f is first default initialized and then assigned a new value:

default constructor 
default constructor
assignment
destructor
destructor

Changing the initialization from the assignment in the constructor body to the initializer list replaces the calls to the default constructor plus assignment operator with a call to the copy constructor:

    bar(foo const & value) : f(value) { }

Adding the preceding line of code produces the following output:

default constructor 
copy constructor
destructor
destructor

For those reasons, at least for other types than the built-in types (such as bool, char, int, float, double or pointers), you should prefer the constructor initializer list. However, to be consistent with your initialization style, you should always prefer the constructor initializer list when that is possible. There are several situations when using the initializer list is not possible; these include the following cases (but the list could be expanded with other cases):

  • If a member has to be initialized with a pointer or reference to the object that contains it, using the this pointer in the initialization list may trigger a warning with some compilers that it is used before the object is constructed.
  • If you have two data members that must contain references to each other.
  • If you want to test an input parameter and throw an exception before initializing a non-static data member with the value of the parameter.

Starting with C++11, non-static data members can be initialized when declared in the class. This is called default member initialization because it is supposed to represent initialization with default values. Default member initialization is intended for constants and for members that are not initialized based on constructor parameters (in other words members whose value does not depend on the way the object is constructed):

    enum class TextFlow { LeftToRight, RightToLeft }; 

struct Control
{
const int DefaultHeight = 20;
const int DefaultWidth = 100;

TextFlow textFlow = TextFlow::LeftToRight;
std::string text;

Control(std::string t) : text(t)
{}
};

In the preceding example, DefaultHeight and DefaultWidth are both constants; therefore, the values do not depend on the way the object is constructed, so they are initialized when declared. The textFlow object is a non-constant non-static data member whose value also does not depend on the way the object is initialized (it could be changed via another member function), therefore, it is also initialized using default member initialization when it is declared. text, on the other hand, is also a non-constant non-static data member, but its initial value depends on the way the object is constructed and therefore it is initialized in the constructor's initializer list using a value passed as an argument to the constructor.

If a data member is initialized both with the default member initialization and constructor initializer list, the latter takes precedence and the default value is discarded. To exemplify this, let's again consider the foo class earlier and the following bar class that uses it:

    struct bar 
{
foo f{"default value"};

bar() : f{"constructor initializer"}
{
}
};

bar b;

The output differs, in this case, as follows, because the value from the default initializer list is discarded, and the object is not initialized twice:

constructor
constructor initializer
destructor
Using the appropriate initialization method for each member leads not only to more efficient code but also to better organized and more readable code.
Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Explore the most important language and library features of C++17, including containers, algorithms, regular expressions, threads, and more,
  • Get going with unit testing frameworks Boost.Test, Google Test and Catch,
  • Extend your C++ knowledge and take your development skills to new heights by making your applications fast, robust, and scalable.

Description

C++ is one of the most widely used programming languages. Fast, efficient, and flexible, it is used to solve many problems. The latest versions of C++ have seen programmers change the way they code, giving up on the old-fashioned C-style programming and adopting modern C++ instead. Beginning with the modern language features, each recipe addresses a specific problem, with a discussion that explains the solution and offers insight into how it works. You will learn major concepts about the core programming language as well as common tasks faced while building a wide variety of software. You will learn about concepts such as concurrency, performance, meta-programming, lambda expressions, regular expressions, testing, and many more in the form of recipes. These recipes will ensure you can make your applications robust and fast. By the end of the book, you will understand the newer aspects of C++11/14/17 and will be able to overcome tasks that are time-consuming or would break your stride while developing.

Who is this book for?

If you want to overcome difficult phases of development with C++ and leverage its features using modern programming practices, then this book is for you. The book is designed for both experienced C++ programmers as well as people with strong knowledge of OOP concepts.

What you will learn

  • Get to know about the new core language features and the problems they were intended to solve
  • Understand the standard support for threading and concurrency and know how to put them on work for daily basic tasks
  • Leverage C++'s features to get increased robustness and performance
  • Explore the widely-used testing frameworks for C++ and implement various useful patterns and idioms
  • Work with various types of strings and look at the various aspects of compilation
  • Explore functions and callable objects with a focus on modern features
  • Leverage the standard library and work with containers, algorithms, and iterators
  • Use regular expressions for find and replace string operations
  • Take advantage of the new filesystem library to work with files and directories
  • Use the new utility additions to the standard library to solve common problems developers encounter including string_view, any , optional and variant types

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : May 15, 2017
Length: 590 pages
Edition : 1st
Language : English
ISBN-13 : 9781786464736
Category :
Languages :

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Product Details

Publication date : May 15, 2017
Length: 590 pages
Edition : 1st
Language : English
ISBN-13 : 9781786464736
Category :
Languages :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€18.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
€189.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
€264.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total €84.96 €95.97 €11.01 saved
Modern C++ Programming Cookbook
€41.99
Mastering C++ Multithreading
€36.99
Mastering C++ Programming
€41.99
Total €84.96€95.97 €11.01 saved Stars icon

Table of Contents

12 Chapters
Learning Modern Core Language Features Chevron down icon Chevron up icon
Working with Numbers and Strings Chevron down icon Chevron up icon
Exploring Functions Chevron down icon Chevron up icon
Preprocessor and Compilation Chevron down icon Chevron up icon
Standard Library Containers, Algorithms, and Iterators Chevron down icon Chevron up icon
General Purpose Utilities Chevron down icon Chevron up icon
Working with Files and Streams Chevron down icon Chevron up icon
Leveraging Threading and Concurrency Chevron down icon Chevron up icon
Robustness and Performance Chevron down icon Chevron up icon
Implementing Patterns and Idioms Chevron down icon Chevron up icon
Exploring Testing Frameworks Chevron down icon Chevron up icon
Bibliography Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Empty star icon 4
(7 Ratings)
5 star 42.9%
4 star 42.9%
3 star 0%
2 star 0%
1 star 14.3%
Filter icon Filter
Top Reviews

Filter reviews by




John Dubchak Jan 31, 2020
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I am enjoying this book very much. The recipes are succinct, well-written, to the point and tell you what you need to know, why and without any extra fluff.Highly recommended for those “C++ Programmers” still writing C++98 code - time to modernize.
Amazon Verified review Amazon
Plotnus Dec 07, 2018
Full star icon Full star icon Full star icon Full star icon Full star icon 5
This is a great book. This is a readable book. It's not as dry and boring as alternatives. It is not about memorizing 'trivia'. For these reasons, this is my favorite C++ book and one I recommend to my friends.-------------I bought this book for interview prep. It's now my favorite book on modern C++.My Background: I am a game programmer who has come to greatly dislike C++.I feel that C++ is a 'trivia' language. By this, I mean to be a good C++ programmer you need to understand and know loads of trivia. It is a language for pedants. Most C++ books cater to this approach. One example is Scott Myers books which introduce 90 'tips'. You can also find this in the workplace when coders quote the standard. I really dislike the 'trivia' aspect of being a good C++ programmer and see it as a failing of the language. C++ makes things complicated.This book helps boil them down. It's not about trivia. It's about examples and explaining what is going on with C++ and being able to use it. It's back to base principles.Each chapter has the sections: "Getting Ready", "How to do it", "How it Works", and "There's more...""Getting Ready" - this gives you about a paragraph of background and primes your mind for the topic."How to do it" - shows you examples of using or doing the thing being discussed. Prepares you for using the topic on your own. This also gives you examples you can play with on your own computer."How it works" - goes into a few pages of detail about the implementation. This is great because by understanding the implementation you can avoid many of the pitfalls."There's more" - gives additional detail.So, why is this my favorite C++ book?I feel it frees me from the 'trivia' prevalent in many C++ books. By showing examples and talking about the implementation it prepares me to reason about what is going on instead of relying on "Item 35" or some other hard to remember fact about proper C++ usage.
Amazon Verified review Amazon
Miaw May 12, 2020
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I am really happy that I own this book and have read it through. Before reading this book I also had some exposure to modern C++ standard/techniques, but it just seemed very intimidating with the enormous amount of new information and philosophy. Also the standard has changed rapidly from one version to another, I was just frustrated about the fast upgrade and felt it is really hard to keep up while attempting to absorb the new ideas. With this book it all started to change, I am really surprised it covers pretty much all the stuff you need to know, or at least gives you enough hints about how to further explore into this new world. After reading this book I feel a lot more confident on new C++ and I have to say it helps me a lot on my work too(I just transitioned from an environment of C++98 to a new place where everything is C++11/14/17 with heavy use of metaprogramming/templates). It even shows you some really cool design patterns and testing framework which really comes in handy, at least for me.I am also surprised it explains well on some really hard/new subjects with fairly limited paragraphs such as memory models used in atomic library and enable_if where both deserve to have their own books. If anything I'd say it probably fails a bit short on move semantics and the use of decltype/declval which in my opinion are also very important stuff in the new standard.Overall I am really satisfied and I feel happy and a lot more knowledgeable when I finished reading it. Highly recommended to all of you who want to catch up on this new wave of C++.
Amazon Verified review Amazon
S. Ghiassy Dec 18, 2017
Full star icon Full star icon Full star icon Full star icon Empty star icon 4
Very good book , helps you with new C++ Standards and coding style. Would highly recommend it to all C++ Programmers, from novice to experienced.
Amazon Verified review Amazon
Bartek F. Jun 15, 2017
Full star icon Full star icon Full star icon Full star icon Empty star icon 4
Disclaimer: I got a free copy from Packt Publishing.Pros: * Clear structure * Cookbook style, so read what you need * Chapters usually starts with some basic recipes and then increase the level of complexity. * Concise examples and details how it works, so not just functions headers. * Modern coding standard, even with C++17 stuff! * C++11, C++14 and C++17 - with clear distinction, an explanation what have changed, etc. * It doesn't have much of 'intro to C++', so you can just jump into intermediate topics! It's not another basic beginner's book. * There are useful 'tips' here and thereCons:* A few typos, repetitions* Chapter about unit testing frameworks could be shorter, but maybe other devs find it useful.* Some recipes are questionable: but that depends on the view/experience. For example: using bitsets. I'd like to see more about performance in the performance chapter.Overall, I like the book. With its clear structure and well-written recipes, it's a great addition to any C++ bookshelf. It's well suited for the target audience: even if you're an expert you'll get a chance to refresh your knowledge and update it with C++14/C++17 content. And If you've just finished some beginner book, you'll find here topics that will move you forward.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.