Modern C++ Tutorial C++11!14!17 - 20 On The Fly - Modern-cpp-tutorial-En-us
Modern C++ Tutorial C++11!14!17 - 20 On The Fly - Modern-cpp-tutorial-En-us
Modern C++ Tutorial C++11!14!17 - 20 On The Fly - Modern-cpp-tutorial-En-us
Changkun Ou (hi[at]changkun.de)
Notice
The content in this PDF le may outdated, please check our website or GitHub repository or the latest
book updates.
License
This work was written by Ou Changkun and licensed under a Creative Commons
Attribution-NonCommercial-NoDerivatives 4.0 International License.
https://creativecommons.org/licenses/by-nc-nd/4.0/
1
2
CONTENTS CONTENTS
Contents
Preace 8
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Targets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Purpose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Further Readings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.1 Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
nullptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
constexpr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
i-switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Initializer list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Structured binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
auto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
decltype . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
decltype(auto) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
i constexpr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.5 Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3
CONTENTS CONTENTS
Extern templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
The “>” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Variadic templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Fold expression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.6 Object-oriented . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Delegate constructor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Inheritance constructor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
override . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
nal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Generic Lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
std::function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Move semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Perect orwarding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4
CONTENTS CONTENTS
Further Readings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Chapter 04 Containers 50
std::array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
std::forward_list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.3 Tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Basic Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Runtime Indexing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
5.2 std::shared_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
5.3 std::unique_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
5.4 std::weak_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Further Readings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Ordinary characters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Special characters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Quantiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Further Readings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
5
CONTENTS CONTENTS
7.3 Future . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Atomic Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Consistency Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Memory Orders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Further Readings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
8.2 std::lesystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Further Readings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
9.3 Literal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Custom Literal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Concept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Contract . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
6
CONTENTS CONTENTS
Range . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Coroutine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Further Readings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Common Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Coding Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Overall Perormance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Code Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Maintainability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Portability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
7
PREFACE
Preace
Introduction
The C++ programming language owns a airly large user group. From the advent o C++98 to the
ocial nalization o C++11, it has continued to stay relevant. C++14/17 is an important complement
and optimization or C++11, and C++20 brings this language to the door o modernization. The
extended eatures o all these new standards are integrated into the C++ language and inuse it with
new vitality. C++ programmers who are still using traditional C++ (this book reers to C++98 and
its previous standards as traditional C++) may even amazed by the act that they are not using the
same language while reading modern C++ code.
Modern C++ (this book reers to C++11/14/17/20) introduces many eatures into traditional
C++ which bring the entire language to a new level o modernization. Modern C++ not only enhances
the usability o the C++ language itsel, but the modication o the auto keyword semantics gives us more
condence in manipulating extremely complex template types. At the same time, a lot o enhancements
have been made to the language runtime. The emergence o Lambda expressions has given C++ the
“closure” eature o “anonymous unctions”, which are in almost all modern programming languages (such
as Python, Swit, etc). It has become commonplace, and the emergence o rvalue reerences has solved
the problem o temporary object eciency that C++ has long been criticized or.
C++17 is the direction that has been promoted by the C++ community in the past three years. It
also points out an important development direction o modern C++ programming. Although it does
not appear as much as C++11, it contains a large number o small and beautiul languages and eatures
(such as structured binding), and the appearance o these eatures once again corrects our programming
paradigm in C++.
Modern C++ also adds a lot o tools and methods to its standard library such as std::thread at
the level o the language itsel, which supports concurrent programming and no longer depends on the
underlying system on dierent platorms. The API implements cross-platorm support at the language
level; std::regex provides ull regular expression support and more. C++98 has been proven to be a
very successul “paradigm”, and the emergence o modern C++ urther promotes this paradigm, making
C++ a better language or system programming and library development. Concepts veriy the compile-
time o template parameters, urther enhancing the usability o the language.
In conclusion, as an advocate and practitioner o C++, we always maintain an open mind to accept
new things, and we can promote the development o C++ aster, making this old and novel language
more vibrant.
Targets
• This book assumes that readers are already amiliar with traditional C++ (i.e. C++98 or earlier),
at least they do not have any diculty in reading traditional C++ code. In other words, those who
have long experience in traditional C++ and people who desire to quickly understand the eatures
8
Purpose CHAPTER 01: TOWARDS MODERN C++
o modern C++ in a short period are well suited to read the book;
• This book introduces to a certain extent o the dark magic o modern C++. However, these magics
are very limited, they are not suitable or readers who want to learn advanced C++. The purpose
o this book is to oer a quick start or modern C++. O course, advanced readers can also use
this book to review and examine themselves on modern C++.
Purpose
The book claims “On the Fly”. It intends to provide a comprehensive introduction to the relevant
eatures regarding modern C++ (beore the 2020s). Readers can choose interesting content according to
the ollowing table o contents to learn and quickly amiliarize themselves with the new eatures that are
available. Readers should aware that all o these eatures are not required. It should be learned when
you need it.
At the same time, instead o grammar-only, the book introduces the historical background as simple
as possible o its technical requirements, which provides great help in understanding why these eatures
come out.
Also, the author would like to encourage that readers should be able to use modern C++ directly
in their new projects and migrate their old projects to modern C++ gradually ater reading the book.
Code
Each chapter o this book has a lot o code. I you encounter problems when writing your own code
with the introductory eatures o the book, you might as well read the source code attached to the book.
You can nd the book here. All the code is organized by chapter, the older name is the chapter number.
Exercises
There are ew exercises At the end o each chapter o the book. It is or testing whether you can
use the knowledge points in the current chapter. You can nd the possible answer to the problem rom
here. The older name is the chapter number.
Compilation Environment: This book will use clang++ as the only compiler used, and always
use the -std=c++2a compilation fag in your code.
> clang++ -v
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.6.0
9
1.1 Deprecated Features CHAPTER 01: TOWARDS MODERN C++
Beore learning modern C++, let’s take a look at the main eatures that have deprecated since
C++11:
Note: Deprecation is not completely unusable, it is only intended to imply that eatures will
disappear rom uture standards and should be avoided. But, the deprecated eatures are still
part o the standard library, and most o the eatures are actually “permanently” reserved
or compatibility reasons.
• The string literal constant is no longer allowed to be assigned to a char *. I you need
to assign and initialize a char * with a string literal constant, you should use const
char * or auto.
• register keyword is deprecated and can be used but no longer has any practical
meaning.
• I a class has a destructor, the properties or which it generates copy constructors and
copy assignment operators are deprecated.
• C language style type conversion is deprecated (ie using (convert_type)) beore vari-
ables, and static_cast, reinterpret_cast, const_cast should be used or type conver-
sion.
• In particular, some o the C standard libraries that can be used are deprecated in
the latest C++17 standard, such as <ccomplex>, <cstdalign>, <cstdbool> and <ctgmath>
etc.
There are also other eatures such as parameter binding (C++11 provides std::bind and
std::function), export etc. are also deprecated. These eatures mentioned above I you have never
used or heard o it, please don’t try to understand them. You should move closer to the
new standard and learn new eatures directly. Ater all, technology is moving orward.
10
1.2 Compatibilities with C CHAPTER 01: TOWARDS MODERN C++
For some orce majeure and historical reasons, we had to use some C code (even old C code) in C++,
or example, Linux system calls. Beore the advent o modern C++, most people talked about “what is
the dierence between C and C++”. Generally speaking, in addition to answering the object-oriented
class eatures and the template eatures o generic programming, there is no other opinion or even a
direct answer. “Almost” is also a lot o people. The Venn diagram in Figure 1.2 roughly answers the C
and C++ related compatibility.
From now on, you should have the idea that “C++ is not a superset o C” in your mind (and
not rom the beginning, later Reerences or urther reading The dierence between C++98 and C99 is
given). When writing C++, you should also avoid using program styles such as void* whenever possible.
When you have to use C, you should pay attention to the use o extern "C", separate the C language
code rom the C++ code, and then uniy the link, or instance:
// foo.h
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
11
1.2 Compatibilities with C CHAPTER 01: TOWARDS MODERN C++
}
#endif
// foo.c
int add(int x, int y) {
return x+y;
}
// 1.1.cpp
#include "foo.h"
#include <iostream>
#include <functional>
int main() {
[out = std::ref(std::cout << "Result from C code: " << add(1, 2))](){
out.get() << ".\n";
}();
return 0;
}
gcc -c foo.c
Compile and output the foo.o le, and link the C++ code to the .o le using clang++ (or both
compile to .o and then link them together):
C = gcc
CXX = clang++
SOURCE_C = foo.c
OBJECTS_C = foo.o
SOURCE_CXX = 1.1.cpp
TARGET = 1.1
LDFLAGS_COMMON = -std=c++2a
12
Further Readings CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
all:
$(C) -c $(SOURCE_C)
$(CXX) $(SOURCE_CXX) $(OBJECTS_C) $(LDFLAGS_COMMON) -o $(TARGET)
clean:
rm -rf *.o $(TARGET)
Note: Indentation in Makefile is a tab instead o a space character. I you copy this
code directly into your editor, the tab may be automatically replaced. Please ensure the
indentation in the Makefile is done by tabs.
I you don’t know the use o Makefile, it doesn’t matter. In this tutorial, you won’t build
code that is written too complicated. You can also read this book by simply using clang++
-std=c++2a on the command line.
I you are new to modern C++, you probably still don’t understand the ollowing small piece o
code above, namely:
[out = std::ref(std::cout << "Result from C code: " << add(1, 2))](){
out.get() << ".\n";
}();
Don’t worry at the moment, we will come to meet them in our later chapters.
Further Readings
When we declare, dene a variable or constant, and control the fow o code, object-oriented unctions,
template programming, etc., beore the runtime, it may happen when writing code or compiler compiling
code. To this end, we usually talk about language usability, which reers to the language behavior
that occurred beore the runtime.
2.1 Constants
nullptr
The purpose o nullptr appears to replace NULL. In a sense, traditional C++ treats NULL and 0
as the same thing, depending on how the compiler denes NULL, and some compilers dene NULL as
13
2.1 Constants CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
C++ does not allow to implicitly convert void * to other types. But i the compiler tries to
dene NULL as ((void*)0), then in the ollowing code:
C++ without the void * implicit conversion has to dene NULL as 0. This still creates a new
problem. Dening NULL to 0 will cause the overloading eature in C++ to be conusing. Consider the
ollowing two foo unctions:
void foo(char*);
void foo(int);
Then the foo(NULL); statement will call foo(int), which will cause the code to be counterintuitive.
To solve this problem, C++11 introduced the nullptr keyword, which is specically used to dis-
tinguish null pointers, 0. The type o nullptr is nullptr_t, which can be implicitly converted to any
pointer or member pointer type, and can be compared equally or unequally with them.
#include <iostream>
#include <type_traits>
int main() {
if (std::is_same<decltype(NULL), decltype(0)>::value)
std::cout << "NULL == 0" << std::endl;
if (std::is_same<decltype(NULL), decltype((void*)0)>::value)
std::cout << "NULL == (void *)0" << std::endl;
if (std::is_same<decltype(NULL), std::nullptr_t>::value)
std::cout << "NULL == nullptr" << std::endl;
void foo(char *) {
std::cout << "foo(char*) is called" << std::endl;
14
2.1 Constants CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
}
void foo(int i) {
std::cout << "foo(int) is called" << std::endl;
}
foo(int) is called
foo(char*) is called
From the output we can see that NULL is dierent rom 0 and nullptr. So, develop the habit o
using nullptr directly.
In addition, in the above code, we used decltype and std::is_same which are modern C++ syntax.
In simple terms, decltype is used or type derivation, and std::is_same is used to compare the equality
o the two types. We will discuss them in detail later in the decltype section.
constexpr
C++ itsel already has the concept o constant expressions, such as 1+2, 3*4. Such expressions
always produce the same result without any side eects. I the compiler can directly optimize and embed
these expressions into the program at compile-time, it will increase the perormance o the program. A
very obvious example is in the denition phase o an array:
#include <iostream>
#define LEN 10
int len_foo() {
int i = 2;
return i;
}
constexpr int len_foo_constexpr() {
return 5;
}
int main() {
char arr_1[10]; // legal
char arr_2[LEN]; // legal
15
2.1 Constants CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
return 0;
}
In the above example, char arr_4[len_2] may be conusing because len_2 has been dened as
a constant. Why is char arr_4[len_2] still illegal? This is because the length o the array in the
C++ standard must be a constant expression, and or len_2, this is a const constant, not a constant
expression, so even i this behavior is supported by most compilers, but it is an illegal behavior, we need
to use the constexpr eature introduced in C++11, which will be introduced next, to solve this problem;
or arr_5, beore C++98 The compiler cannot know that len_foo() actually returns a constant at
runtime, which causes illegal production.
Note that most compilers now have their compiler optimizations. Many illegal behaviors
become legal under the compiler’s optimization. I you need to reproduce the error, you need
to use the old version o the compiler.
C++11 provides constexpr to let the user explicitly declare that the unction or object constructor
will become a constant expression at compile time. This keyword explicitly tells the compiler that it
should veriy that len_foo should be a compile-time constant expression.
Starting with C++14, the constexpr unction can use simple statements such as local variables,
loops, and branches internally. For example, the ollowing code cannot be compiled under the C++11
standard:
16
2.2 Variables and initialization CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
To do this, we can write a simplied version like this to make the unction available rom C++11:
i-switch
In traditional C++, the declaration o a variable can declare a temporary variable int even though it
can be located anywhere, even within a for statement, but there is always no way to declare a temporary
variable in the if and switch statements. E.g:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {1, 2, 3, 4};
17
2.2 Variables and initialization CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
In the above code, we can see that the itr variable is dened in the scope o the entire main(),
which causes us to rename the other when a variable need to traverse the entire std::vector again.
C++17 eliminates this limitation so that we can do this in i(or switch):
Initializer list
Initialization is a very important language eature, the most common one is when the object is
initialized. In traditional C++, dierent objects have dierent initialization methods, such as ordinary
arrays, PODs (Plain Old Data, i.e. classes without constructs, destructors, and virtual unctions) Or
struct type can be initialized with {}, which is what we call the initialization list. For the initialization
o the class object, you need to use the copy construct, or you need to use (). These dierent methods
are specic to each other and cannot be generic. E.g:
#include <iostream>
#include <vector>
class Foo {
public:
int value_a;
int value_b;
Foo(int a, int b) : value_a(a), value_b(b) {}
};
int main() {
// before C++11
int arr[3] = {1, 2, 3};
Foo foo(1, 2);
std::vector<int> vec = {1, 2, 3, 4, 5};
18
2.2 Variables and initialization CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
To solve this problem, C++11 rst binds the concept o the initialization list to the type and calls
it std::initializer_list, allowing the constructor or other unction to use the initialization list like
a parameter, which is the initialization o class objects provides a unied bridge between normal arrays
and POD initialization methods, such as:
#include <initializer_list>
#include <vector>
#include <iostream>
class MagicFoo {
public:
std::vector<int> vec;
MagicFoo(std::initializer_list<int> list) {
for (std::initializer_list<int>::iterator it = list.begin();
it != list.end(); ++it)
vec.push_back(*it);
}
};
int main() {
// after C++11
MagicFoo magicFoo = {1, 2, 3, 4, 5};
This constructor is called the initialize list constructor, and the type with this constructor will be
specially taken care o during initialization.
In addition to the object construction, the initialization list can also be used as a ormal parameter
o a normal unction, or example:
public:
void foo(std::initializer_list<int> list) {
for (std::initializer_list<int>::iterator it = list.begin();
it != list.end(); ++it) vec.push_back(*it);
19
2.3 Type inerence CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
magicFoo.foo({6,7,8,9});
Second, C++11 also provides a uniorm syntax or initializing arbitrary objects, such as:
Structured binding
Structured bindings provide unctionality similar to the multiple return values provided in other
languages. In the chapter on containers, we will learn that C++11 has added a std::tuple container
or constructing a tuple that encloses multiple return values. But the faw is that C++11/14 does not
provide a simple way to get and dene the elements in the tuple rom the tuple, although we can unpack
the tuple using std::tie But we still have to be very clear about how many objects this tuple contains,
what type o each object is, very troublesome.
C++17 completes this setting, and the structured bindings let us write code like this:
#include <iostream>
#include <tuple>
int main() {
auto [x, y, z] = f();
std::cout << x << ", " << y << ", " << z << std::endl;
return 0;
}
The auto type derivation is described in the auto type inerence section.
In traditional C and C++, the types o parameters must be clearly dened, which does not help us
to quickly encode, especially when we are aced with a large number o complex template types, we must
indicate the type o variables to proceed. Subsequent coding, which not only slows down our development
eciency but also makes the code stinking and long.
C++11 introduces the two keywords auto and decltype to implement type derivation, letting the
compiler worry about the type o the variable. This makes C++ the same as other modern programming
languages, in a way that provides the habit o not having to worry about variable types.
20
2.3 Type inerence CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
auto
auto has been in C++ or a long time, but it always exists as an indicator o a storage type,
coexisting with register. In traditional C++, i a variable is not declared as a register variable, it
is automatically treated as an auto variable. And with register being deprecated (used as a reserved
keyword in C++17 and later used, it doesn’t currently make sense), the semantic change to auto is very
natural.
One o the most common and notable examples o type derivation using auto is the iterator. You
should see the lengthy iterative writing in traditional C++ in the previous section:
// before C++11
// cbegin() returns vector<int>::const_iterator
// and therefore it is type vector<int>::const_iterator
for(vector<int>::const_iterator it = vec.cbegin(); it != vec.cend(); ++it)
#include <initializer_list>
#include <vector>
#include <iostream>
class MagicFoo {
public:
std::vector<int> vec;
MagicFoo(std::initializer_list<int> list) {
for (auto it = list.begin(); it != list.end(); ++it) {
vec.push_back(*it);
}
}
};
int main() {
MagicFoo magicFoo = {1, 2, 3, 4, 5};
std::cout << "magicFoo: ";
for (auto it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) {
std::cout << *it << ", ";
}
std::cout << std::endl;
return 0;
}
21
2.3 Type inerence CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
auto i = 5; // i as int
auto arr = new auto(10); // arr as int *
Since C++ 20, auto can even be used as unction arguments. Consider the ollowing example:
decltype
The decltype keyword is used to solve the deect that the auto keyword can only type the variable.
Its usage is very similar to typeof:
decltype(expression)
auto x = 1;
auto y = 2;
decltype(x+y) z;
You have seen in the previous example that decltype is used to iner the usage o the type. The
ollowing example is to determine i the above variables x, y, z are o the same type:
if (std::is_same<decltype(x), int>::value)
std::cout << "type x == int" << std::endl;
if (std::is_same<decltype(x), float>::value)
std::cout << "type x == float" << std::endl;
if (std::is_same<decltype(x), decltype(z)>::value)
std::cout << "type z == type x" << std::endl;
22
2.3 Type inerence CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
Among them, std::is_same<T, U> is used to determine whether the two types T and U are equal.
The output is:
type x == int
type z == type x
You may think that when we introduce auto, we have already mentioned that auto cannot be used
or unction arguments or type derivation. Can auto be used to derive the return type o a unction?
Still consider an example o an add unction, which we have to write in traditional C++:
Note: There is no dierence between typename and class in the template parameter list.
Beore the keyword typename appears, class is used to dene the template parameters. How-
ever, when dening a variable with nested dependency type in the template, you need to use
typename to eliminate ambiguity.
Such code is very ugly because the programmer must explicitly indicate the return type when using
this template unction. But in act, we don’t know what kind o operation the add() unction will do,
and what kind o return type to get.
This problem was solved in C++11. Although you may immediately react to using decltype to
derive the type o x+y, write something like this:
decltype(x+y) add(T x, U y)
But in act, this way o writing can not be compiled. This is because x and y have not been dened
when the compiler reads decltype(x+y). To solve this problem, C++11 also introduces a trailing return
type, which uses the auto keyword to post the return type:
The good news is that rom C++14 it is possible to directly derive the return value o a normal
unction, so the ollowing way becomes legal:
23
2.3 Type inerence CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
// after c++11
auto w = add2<int, double>(1, 2.0);
if (std::is_same<decltype(w), double>::value) {
std::cout << "w is double: ";
}
std::cout << w << std::endl;
// after c++14
auto q = add3<double, int>(1.0, 2);
std::cout << "q: " << q << std::endl;
decltype(auto)
To understand it you need to know the concept o parameter orwarding in C++, which we
will cover in detail in the Language Runtime Enhancements chapter, and you can come back
to the contents o this section later.
In simple terms, decltype(auto) is mainly used to derive the return type o a orwarding unction or
package, which does not require us to explicitly speciy the parameter expression o decltype. Consider
the ollowing example, when we need to wrap the ollowing two unctions:
std::string lookup1();
std::string& lookup2();
In C++11:
std::string look_up_a_string_1() {
return lookup1();
}
std::string& look_up_a_string_2() {
return lookup2();
}
With decltype(auto), we can let the compiler do this annoying parameter orwarding:
24
2.4 Control fow CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
decltype(auto) look_up_a_string_1() {
return lookup1();
}
decltype(auto) look_up_a_string_2() {
return lookup2();
}
i constexpr
As we saw at the beginning o this chapter, we know that C++11 introduces the constexpr keyword,
which compiles expressions or unctions into constant results. A natural idea is that i we introduce this
eature into the conditional judgment, let the code complete the branch judgment at compile-time, can
it make the program more ecient? C++17 introduces the constexpr keyword into the if statement,
allowing you to declare the condition o a constant expression in your code. Consider the ollowing code:
#include <iostream>
template<typename T>
auto print_type_info(const T& t) {
if constexpr (std::is_integral<T>::value) {
return t + 1;
} else {
return t + 0.001;
}
}
int main() {
std::cout << print_type_info(5) << std::endl;
std::cout << print_type_info(3.14) << std::endl;
}
25
2.5 Templates CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
Finally, C++11 introduces a range-based iterative method, and we can write loops that are as
concise as Python, and we can urther simpliy the previous example:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {1, 2, 3, 4};
if (auto itr = std::find(vec.begin(), vec.end(), 3); itr != vec.end()) *itr = 4;
for (auto element : vec)
std::cout << element << std::endl; // read only
for (auto &element : vec) {
element += 1; // writeable
}
for (auto element : vec)
std::cout << element << std::endl; // read only
}
2.5 Templates
C++ templates have always been a special art o the language, and templates can even be used
independently as a new language. The philosophy o the template is to throw all the problems that can
be processed at compile time into the compile time, and only deal with those core dynamic services at
runtime, to greatly optimize the perormance o the runtime. Thereore, templates are also regarded by
many as one o the black magic o C++.
Extern templates
In traditional C++, templates are instantiated by the compiler only when they are used. In other
words, as long as a ully dened template is encountered in the code compiled in each compilation unit
(le), it will be instantiated. This results in an increase in compile time due to repeated instantiations.
Also, we have no way to tell the compiler not to trigger the instantiation o the template.
To this end, C++11 introduces an external template that extends the syntax o the original manda-
tory compiler to instantiate a template at a specic location, allowing us to explicitly tell the compiler
when to instantiate the template:
26
2.5 Templates CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
The “>”
In the traditional C++ compiler, >> is always treated as a right shit operator. But actually we can
easily write the code or the nested template:
std::vector<std::vector<int>> matrix;
This is not compiled under the traditional C++ compiler, and C++11 starts with continuous right
angle brackets that become legal and can be compiled successully. Even the ollowing writing can be
compiled by:
template<bool T>
class MagicType {
bool magic = T;
};
// in main function:
std::vector<MagicType<(1>2)>> magic; // legal, but not recommended
Beore you understand the type alias template, you need to understand the dierence between
“template” and “type”. Careully understand this sentence: Templates are used to generate types.
In traditional C++, typedef can dene a new name or the type, but there is no way to dene a new
name or the template. Because the template is not a type. E.g:
// not allowed
template<typename T>
typedef MagicType<std::vector<T>, std::string> FakeDarkMagic;
C++11 uses using to introduce the ollowing orm o writing, and at the same time supports the
same eect as the traditional typedef:
27
2.5 Templates CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
Usually, we use typedef to dene the alias syntax: typedef original name new name;, but
the denition syntax or aliases such as unction pointers is dierent, which usually causes a
certain degree o diculty or direct reading.
int main() {
TrueDarkMagic<bool> you;
}
Variadic templates
The template has always been one o C++’s unique Black Magic. In traditional C++, both a
class template and a unction template could only accept a xed set o template parameters as specied;
C++11 added a new representation, allowing any number, template parameters o any category, and
there is no need to x the number o parameters when dening.
The template class Magic object can accept an unrestricted number o typename as a ormal param-
eter o the template, such as the ollowing denition:
class Magic<int,
std::vector<int>,
std::map<std::string,
std::vector<int>>> darkMagic;
Since it is arbitrary, a template parameter with a number o 0 is also possible: class Magic<>
nothing;.
I you do not want to generate 0 template parameters, you can manually dene at least one template
parameter:
The variable length parameter template can also be directly adjusted to the template unction. The
printf unction in the traditional C, although it can also reach the call o an indenite number o ormal
parameters, is not class sae. In addition to the variable-length parameter unctions that dene class
saety, C++11 can also make print-like unctions naturally handle objects that are not sel-contained.
In addition to the use o ... in the template parameters to indicate the indenite length o the template
28
2.5 Templates CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
parameters, the unction parameters also use the same representation to represent the indenite length
parameters, which provides a convenient means or us to simply write variable length parameter unctions,
such as:
Then we dene variable length template parameters, how to unpack the parameters?
#include <iostream>
template<typename... Ts>
void magic(Ts... args) {
std::cout << sizeof...(args) << std::endl;
}
magic(); // 0
magic(1); // 1
magic(1, ""); // 2
Second, the parameters are unpacked. So ar there is no simple way to process the parameter
package, but there are two classic processing methods:
Recursion is a very easy way to think o and the most classic approach. This method continually
recursively passes template parameters to the unction, thereby achieving the purpose o recursively
traversing all template parameters:
#include <iostream>
template<typename T0>
void printf1(T0 value) {
std::cout << value << std::endl;
}
template<typename T, typename... Ts>
void printf1(T value, Ts... args) {
std::cout << value << std::endl;
printf1(args...);
}
int main() {
printf1(1, 2, "123", 1.1);
return 0;
}
29
2.5 Templates CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
You should eel that this is very cumbersome. Added support or variable parameter template
expansion in C++17, so you can write printf in a unction:
In act, sometimes we use variable parameter templates, but we don’t necessarily need to
traverse the parameters one by one. We can use the eatures o std::bind and perect
orwarding to achieve the binding o unctions and parameters, thus achieving success. The
purpose o the call.
Recursive template unctions are standard practice, but the obvious drawback is that you must
dene a unction that terminates recursion.
Here is a description o the black magic that is expanded using the initialization list:
In this code, the initialization list provided in C++11 and the properties o the Lambda expression
(mentioned in the next section) are additionally used.
By initializing the list, (lambda expression, value)... will be expanded. Due to the appear-
ance o the comma expression, the previous lambda expression is executed rst, and the output o the
parameter is completed. To avoid compiler warnings, we can explicitly convert std::initializer_list
to void.
Fold expression
In C++ 17, this eature o the variable length parameter is urther brought to the expression,
consider the ollowing example:
30
2.5 Templates CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
#include <iostream>
template<typename ... T>
auto sum(T ... t) {
return (t + ...);
}
int main() {
std::cout << sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << std::endl;
}
What we mainly mentioned above is a orm o template parameters: type template parameters.
The parameters o the template T and U are specic types. But there is also a common orm
o template parameter that allows dierent literals to be template parameters, i.e. non-type template
parameters:
In this orm o template parameters, we can pass 100 as a parameter to the template. Ater C++11
introduced the eature o type derivation, we will naturally ask, since the template parameters here.
Passing with a specic literal, can the compiler assist us in type derivation, By using the placeholder
auto, there is no longer a need to explicitly speciy the type? Fortunately, C++17 introduces this eature,
and we can indeed use the auto keyword to let the compiler assist in the completion o specic types o
derivation. E.g:
31
2.6 Object-oriented CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
return;
}
int main() {
foo<10>(); // value as int
}
2.6 Object-oriented
Delegate constructor
C++11 introduces the concept o a delegate construct, which allows a constructor to call another
constructor in a constructor in the same class, thus simpliying the code:
#include <iostream>
class Base {
public:
int value1;
int value2;
Base() {
value1 = 1;
}
Base(int value) : Base() { // delegate Base() constructor
value2 = value;
}
};
int main() {
Base b(2);
std::cout << b.value1 << std::endl;
std::cout << b.value2 << std::endl;
}
Inheritance constructor
In traditional C++, constructors need to pass arguments one by one i they need inheritance, which
leads to ineciency. C++11 introduces the concept o inheritance constructors using the keyword using:
#include <iostream>
class Base {
public:
int value1;
32
2.6 Object-oriented CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
int value2;
Base() {
value1 = 1;
}
Base(int value) : Base() { // delegate Base() constructor
value2 = value;
}
};
class Subclass : public Base {
public:
using Base::Base; // inheritance constructor
};
int main() {
Subclass s(3);
std::cout << s.value1 << std::endl;
std::cout << s.value2 << std::endl;
}
struct Base {
virtual void foo();
};
struct SubClass: Base {
void foo();
};
SubClass::foo may not be a programmer trying to overload a virtual unction, just adding a
unction with the same name. Another possible scenario is that when the virtual unction o the base
class is deleted, the subclass owns the old unction and no longer overloads the virtual unction and turns
it into a normal class method, which has catastrophic consequences.
C++11 introduces the two keywords override and final to prevent this rom happening.
override
When overriding a virtual unction, introducing the override keyword will explicitly tell the com-
piler to overload, and the compiler will check i the base unction has such a virtual unction, otherwise
it will not compile:
33
2.6 Object-oriented CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
struct Base {
virtual void foo(int);
};
struct SubClass: Base {
virtual void foo(int) override; // legal
virtual void foo(float) override; // illegal, no virtual function in super class
};
fnal
final is to prevent the class rom being continued to inherit and to terminate the virtual unction
to continue to be overloaded.
struct Base {
virtual void foo() final;
};
struct SubClass1 final: Base {
}; // legal
In traditional C++, i the programmer does not provide it, the compiler will deault to generating
deault constructors, copy constructs, assignment operators, and destructors or the object. Besides, C++
also denes operators such as new delete or all classes. This part o the unction can be overridden
when the programmer needs it.
This raises some requirements: the ability to accurately control the generation o deault unctions
cannot be controlled. For example, when copying a class is prohibited, the copy constructor and the
assignment operator must be declared as private. Trying to use these undened unctions will result in
compilation or link errors, which is a very unconventional way.
Also, the deault constructor generated by the compiler cannot exist at the same time as the user-
dened constructor. I the user denes any constructor, the compiler will no longer generate the deault
constructor, but sometimes we want to have both constructors at the same time, which is awkward.
C++11 provides a solution to the above requirements, allowing explicit declarations to take or reject
unctions that come with the compiler. E.g:
34
2.6 Object-oriented CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
class Magic {
public:
Magic() = default; // explicit let compiler use default constructor
Magic& operator=(const Magic&) = delete; // explicit declare refuse constructor
Magic(int magic_number);
}
In traditional C++, enumerated types are not type-sae, and enumerated types are treated as
integers, which allows two completely dierent enumerated types to be directly compared (although the
compiler gives the check, but not all), ** Even the enumeration value names o dierent enum types in
the same namespace cannot be the same**, which is usually not what we want to see.
C++11 introduces an enumeration class and declares it using the syntax o enum class:
The enumeration thus dened implements type saety. First, it cannot be implicitly converted to an
integer, nor can it be compared to integer numbers, and it is even less likely to compare enumerated values
o dierent enumerated types. But i the values specied are the same between the same enumerated
values, then you can compare:
In this syntax, the enumeration type is ollowed by a colon and a type keyword to speciy the type
o the enumeration value in the enumeration, which allows us to assign a value to the enumeration (int
is used by deault when not specied).
And we want to get the value o the enumeration value, we will have to explicitly type conversion,
but we can overload the << operator to output, you can collect the ollowing code snippet:
#include <iostream>
template<typename T>
std::ostream& operator<<(
typename std::enable_if<std::is_enum<T>::value,
35
Conclusion CHAPTER 02: LANGUAGE USABILITY ENHANCEMENTS
Conclusion
This section introduces the enhancements to language usability in modern C++, which I believe are
the most important eatures that almost everyone needs to know and use:
Exercises
1. Using structured binding, implement the ollowing unctions with just one line o unction code:
#include <string>
#include <map>
#include <iostream>
36
CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
2. Try to implement a unction or calculating the mean with Fold Expression, allowing any arguments
to be passed in.
Lambda expressions are one o the most important eatures in modern C++, and Lambda expres-
sions provide a eature like anonymous unctions. Anonymous unctions are used when a unction is
needed, but you don’t want to use a name to call a unction. There are many, many scenes like this. So
anonymous unctions are almost standard in modern programming languages.
Basics
[capture list] (parameter list) mutable(optional) exception attribute -> return type {
// function body
}
The above grammar rules are well understood except or the things in [capture list], except that
the unction name o the general unction is omitted. The return value is in the orm o a -> (we have
already mentioned this in the tail return type earlier in the previous section).
The so-called capture list can be understood as a type o parameter. The internal unction body o
a lambda expression cannot use variables outside the body o the unction by deault. At this time, the
capture list can serve to transer external data. According to the behavior passed, the capture list is also
divided into the ollowing types:
1. Value capture Similar to parameter passing, the value capture is based on the act that the variable
can be copied, except that the captured variable is copied when the lambda expression is created, not
when it is called:
void lambda_value_capture() {
int value = 1;
auto copy_value = [value] {
return value;
};
value = 100;
37
3.1 Lambda Expression CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
2. Reerence capture Similar to a reerence pass, the reerence capture saves the reerence and the
value changes.
void lambda_reference_capture() {
int value = 1;
auto copy_value = [&value] {
return value;
};
value = 100;
auto stored_value = copy_value();
std::cout << "stored_value = " << stored_value << std::endl;
// At this moment, stored_value == 100, value == 100.
// Because copy_value stores reference
}
3. Implicit capture Manually writing a capture list is sometimes very complicated. This mechanical
work can be handled by the compiler. At this point, you can write a & or = to the compiler to declare
the reerence or value capture.
To summarize, capture provides the ability or lambda expressions to use external values. The our
most common orms o capture lists can be:
4. Expression capture
This section needs to understand the rvalue reerences and smart pointers that will be men-
tioned later.
The value captures and reerence captures mentioned above are variables that have been declared
in the outer scope, so these capture methods capture the lvalue and not capture the rvalue.
C++14 gives us the convenience o allowing the captured members to be initialized with arbitrary
expressions, which allows the capture o rvalues. The type o the captured variable being declared is
38
3.2 Function Object Wrapper CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
judged according to the expression, and the judgment is the same as using auto:
#include <iostream>
#include <memory> // std::make_unique
#include <utility> // std::move
void lambda_expression_capture() {
auto important = std::make_unique<int>(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
return x+y+v1+(*v2);
};
std::cout << add(3,4) << std::endl;
}
In the above code, important is an exclusive pointer that cannot be caught by value capture using
=. At this time we need to transer it to the rvalue and initialize it in the expression.
Generic Lambda
In the previous section, we mentioned that the auto keyword cannot be used in the parameter list
because it would confict with the unctionality o the template. But lambda expressions are not regular
unctions, without urther specication on the typed parameter list, lambda expressions cannot utilize
templates. Fortunately, this trouble only exists in C++11, starting with C++14. The ormal parameters
o the lambda unction can use the auto keyword to utilize template generics:
void lambda_generic() {
auto generic = [](auto x, auto y) {
return x+y;
};
Although the eatures are part o the standard library and not ound in runtime, it enhances the
runtime capabilities o the C++ language. This part o the content is also very important, so put it here
or the introduction.
39
3.2 Function Object Wrapper CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
std::function
The essence o a Lambda expression is an object o a class type (called a closure type) that is similar
to a unction object type (called a closure object). When the capture list o a Lambda expression is
empty, the closure object can also be converted to a unction pointer value or delivery, or example:
#include <iostream>
using foo = void(int); // function pointer
void functional(foo f) {
f(1);
}
int main() {
auto f = [](int value) {
std::cout << value << std::endl;
};
functional(f); // call by function pointer
f(1); // call by lambda expression
return 0;
}
The above code gives two dierent orms o invocation, one is to call Lambda as a unction type,
and the other is to directly call a Lambda expression. In C++11, these concepts are unied. The
type o object that can be called is collectively called the callable type. This type is introduced by
std::function.
C++11 std::function is a generic, polymorphic unction wrapper whose instances can store, copy,
and call any target entity that can be called. It is also an existing callable to C++. A type-sae package
o entities (relatively, the call to a unction pointer is not type-sae), in other words, a container o
unctions. When we have a container or unctions, we can more easily handle unctions and unction
pointers as objects. e.g:
#include <functional>
#include <iostream>
int main() {
// std::function wraps a function that take int paremeter and returns int value
std::function<int(int)> func = foo;
40
3.3 rvalue Reerence CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
And std::bind is used to bind the parameters o the unction call. It solves the requirement that
we may not always be able to get all the parameters o a unction at one time. Through this unction,
we can Part o the call parameters are bound to the unction in advance to become a new object, and
then complete the call ater the parameters are complete. e.g:
Tip: Note the magic o the auto keyword. Sometimes we may not be amiliar with the
return type o a unction, but we can circumvent this problem by using auto.
rvalue reerences are one o the important eatures introduced by C++11 that are synonymous with
Lambda expressions. Its introduction solves a large number o historical issues in C++. Eliminating extra
overhead such as std::vector, std::string, and making the unction object container std::function
possible.
To understand what the rvalue reerence is all about, you must have a clear understanding o the
lvalue and the rvalue.
lvalue, let value, as the name implies, is the value to the let o the assignment symbol. To be
precise, an lvalue is a persistent object that still exists ater an expression (not necessarily an assignment
41
3.3 rvalue Reerence CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
expression).
Rvalue, right value, the value on the right reers to the temporary object that no longer exists
ater the expression ends.
In C++11, in order to introduce powerul rvalue reerences, the concept o rvalue values is urther
divided into: prvalue, and xvalue.
pvalue, pure rvalue, purely rvalue, either purely literal, such as 10, true; either the result o
the evaluation is equivalent to a literal or anonymous temporary object, or example 1+2. Temporary
variables returned by non-reerences, temporary variables generated by operation expressions, original
literals, and Lambda expressions are all pure rvalue values.
Note that a literal (except a string literal) is a prvalue. However, a string literal is an lvalue with
type const char array. Consider the ollowing examples:
#include <type_traits>
int main() {
// Correct. The type of "01234" is const char [6], so it is an lvalue
const char (&left)[6] = "01234";
However, an array can be implicitly converted to a corresponding pointer.The result, i not an lvalue
reerence, is an rvalue (xvalue i the result is an rvalue reerence, prvalue otherwise):
xvalue, expiring value is the concept proposed by C++11 to introduce rvalue reerences (so in
traditional C++, pure rvalue and rvalue are the same concepts), a value that is destroyed but can be
moved.
It would be a little hard to understand the xvalue, let’s look at the code like this:
std::vector<int> foo() {
std::vector<int> temp = {1, 2, 3, 4};
42
3.3 rvalue Reerence CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
return temp;
}
std::vector<int> v = foo();
In such code, as ar as the traditional understanding is concerned, the return value temp o the
unction foo is internally created and then assigned to v, whereas when v gets this object, the entire
temp is copied. And then destroy temp, i this temp is very large, this will cause a lot o extra overhead
(this is the problem that traditional C++ has been criticized or). In the last line, v is the lvalue, and
the value returned by foo() is the rvalue (which is also a pure rvalue).
However, v can be caught by other variables, and the return value generated by foo() is used as a
temporary value. Once copied by v, it will be destroyed immediately, and cannot be obtained or modied.
The xvalue denes behavior in which temporary values can be identied while being able to be moved.
Ater C++11, the compiler did some work or us, where the lvalue temp is subjected to this implicit
rvalue conversion, equivalent to static_cast<std::vector<int> &&>(temp), where v here moves the
value returned by foo locally. This is the move semantics we will mention later.
To get a xvalue, you need to use the declaration o the rvalue reerence: T &&, where T is the type.
The statement o the rvalue reerence extends the liecycle o this temporary value, and as long as the
variable is alive, the xvalue will continue to survive.
C++11 provides the std::move method to unconditionally convert lvalue parameters to rvalues.
With it we can easily get a rvalue temporary object, or example:
#include <iostream>
#include <string>
int main()
{
std::string lv1 = "string,"; // lv1 is a lvalue
// std::string&& r1 = lv1; // illegal, rvalue can't ref to lvalue
std::string&& rv1 = std::move(lv1); // legal, std::move can convert lvalue to rvalue
std::cout << rv1 << std::endl; // string,
43
3.3 rvalue Reerence CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
const std::string& lv2 = lv1 + lv1; // legal, const lvalue reference can
// extend temp variable's lifecycle
// lv2 += "Test"; // illegal, const ref can't be modified
std::cout << lv2 << std::endl; // string,string,
return 0;
}
Note that there is a very interesting historical issue here, let’s look at the ollowing code:
#include <iostream>
int main() {
// int &a = std::move(1); // illegal, non-const lvalue reference cannot ref rvalue
const int &b = std::move(1); // legal, const lvalue reference can
The rst question, why not allow non-constant reerences to bind to non-lvalues? This is because
there is a logic error in this approach:
Since int& can’t reerence a parameter o type double, you must generate a temporary value to
hold the value o s. Thus, when increase() modies this temporary value, s itsel is not modied ater
the call is completed.
44
3.3 rvalue Reerence CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
The second question, why do constant reerences allow binding to non-lvalues? The reason is simple
because Fortran needs it.
Move semantics
Traditional C++ has designed the concept o copy/copy or class objects through copy constructors
and assignment operators, but to implement the movement o resources, The caller must use the method
o copying and then destructing rst, otherwise, you need to implement the interace o the mobile object
yoursel. Imagine moving your home directly to your new home instead o copying everything (rebuy)
to your new home. Throwing away (destroying) all the original things is a very anti-human thing.
Traditional C++ does not distinguish between the concepts o “mobile” and “copy”, resulting in a
large amount o data copying, wasting time and space. The appearance o rvalue reerences solves the
conusion o these two concepts, or example:
#include <iostream>
class A {
public:
int *pointer;
A():pointer(new int(1)) {
std::cout << "construct" << pointer << std::endl;
}
A(A& a):pointer(new int(*a.pointer)) {
std::cout << "copy" << pointer << std::endl;
} // meaningless object copy
A(A&& a):pointer(a.pointer) {
a.pointer = nullptr;
std::cout << "move" << pointer << std::endl;
}
~A(){
std::cout << "destruct" << pointer << std::endl;
delete pointer;
}
};
// avoid compiler optimization
A return_rvalue(bool test) {
A a,b;
if(test) return a; // equal to static_cast<A&&>(a);
else return b; // equal to static_cast<A&&>(b);
}
int main() {
A obj = return_rvalue(false);
45
3.3 rvalue Reerence CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
1. First construct two A objects inside return_rvalue, and get the output o the two constructors;
2. Ater the unction returns, it will generate a xvalue, which is reerenced by the moving structure
o A (A(A&&)), thus extending the lie cycle, and taking the pointer in the rvalue and saving it to
obj. In the middle, the pointer to the xvalue is set to nullptr, which prevents the memory area
rom being destroyed.
This avoids meaningless copy constructs and enhances perormance. Let’s take a look at an example
involving a standard library:
int main() {
return 0;
}
46
3.3 rvalue Reerence CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
Perect orwarding
As we mentioned earlier, the rvalue reerence o a declaration is actually an lvalue. This creates
problems or us to parameterize (pass):
#include <iostream>
#include <utility>
void reference(int& v) {
std::cout << "lvalue reference" << std::endl;
}
void reference(int&& v) {
std::cout << "rvalue reference" << std::endl;
}
template <typename T>
void pass(T&& v) {
std::cout << " normal param passing: ";
reference(v);
}
int main() {
std::cout << "rvalue pass:" << std::endl;
pass(1);
return 0;
}
For pass(1), although the value is the rvalue, since v is a reerence, it is also an lvalue. Thereore
reference(v) will call reference(int&) and output lvalue. For pass(l), l is an lvalue, why is it
successully passed to pass(T&&)?
This is based on the reerence collapsing rule: In traditional C++, we are not able to continue to
reerence a reerence type. However, C++ has relaxed this practice with the advent o rvalue reerences,
resulting in a reerence collapse rule that allows us to reerence reerences, both lvalue and rvalue. But
ollow the rules below:
Function parameter type Argument parameter type Post-derivation unction parameter type
47
3.3 rvalue Reerence CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
Thereore, the use o T&& in a template unction may not be able to make an rvalue reerence, and
when a lvalue is passed, a reerence to this unction will be derived as an lvalue. More precisely, no
matter what type o reerence the template parameter is, the template parameter can be
derived as a right reerence type i and only i the argument type is a right reerence. This makes
v successul delivery o lvalues.
Perect orwarding is based on the above rules. The so-called perect orwarding is to let us pass the
parameters, Keep the original parameter type (lvalue reerence keeps lvalue reerence, rvalue reerence
keeps rvalue reerence). To solve this problem, we should use std::forward to orward (pass) the
parameters:
#include <iostream>
#include <utility>
void reference(int& v) {
std::cout << "lvalue reference" << std::endl;
}
void reference(int&& v) {
std::cout << "rvalue reference" << std::endl;
}
template <typename T>
void pass(T&& v) {
std::cout << " normal param passing: ";
reference(v);
std::cout << " std::move param passing: ";
reference(std::move(v));
std::cout << " std::forward param passing: ";
reference(std::forward<T>(v));
std::cout << "static_cast<T&&> param passing: ";
reference(static_cast<T&&>(v));
}
int main() {
std::cout << "rvalue pass:" << std::endl;
pass(1);
return 0;
}
48
3.3 rvalue Reerence CHAPTER 03: LANGUAGE RUNTIME ENHANCEMENTS
rvalue pass:
normal param passing: lvalue reference
std::move param passing: rvalue reference
std::forward param passing: rvalue reference
static_cast<T&&> param passing: rvalue reference
lvalue pass:
normal param passing: lvalue reference
std::move param passing: rvalue reference
std::forward param passing: lvalue reference
static_cast<T&&> param passing: lvalue reference
Regardless o whether the pass parameter is an lvalue or an rvalue, the normal pass argument will
orward the argument as an lvalue. So std::move will always accept an lvalue, which orwards the call
to reference(int&&) to output the rvalue reerence.
Only std::forward does not cause any extra copies and perectly orwards (passes) the arguments
o the unction to other unctions that are called internally.
std::forward is the same as std::move, and nothing is done. std::move simply converts the lvalue
to the rvalue. std::forward is just a simple conversion o the parameters. From the point o view o
the phenomenon, std::forward<T>(v) is the same as static_cast<T&&>(v).
Readers may be curious as to why a statement can return values or two types o returns. Let’s take
a quick look at the concrete implementation o std::forward. std::forward contains two overloads:
template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
When std::forward accepts an lvalue, _Tp is deduced to the lvalue, so the return value is the lvalue;
and when it accepts the rvalue, _Tp is derived as an rvalue reerence, and based on the collapse rule, the
49
Conclusion CHAPTER 04 CONTAINERS
return value becomes the rvalue o && + &&. It can be seen that the principle o std::forward is to
make clever use o the dierences in template type derivation.
At this point, we can answer the question: Why is auto&& the saest way to use looping statements?
Because when auto is pushed to a dierent lvalue and rvalue reerence, the collapsed combination with
&& is perectly orwarded.
Conclusion
This chapter introduces the most important runtime enhancements in modern C++, and I believe
that all the eatures mentioned in this section are worth knowing:
Lambda expression
Further Readings
Chapter 04 Containers
std::array
When you see this container, you will have this problem:
First, answer the rst question. Unlike std::vector, the size o the std::array object is xed. I
the container size is xed, then the std::array container can be used rst. Also, since std::vector
is automatically expanded, when a large amount o data is stored, and the container is deleted, The
container does not automatically return the corresponding memory o the deleted element. In this case,
you need to manually run shrink_to_fit() to release this part o the memory.
std::vector<int> v;
std::cout << "size:" << v.size() << std::endl; // output 0
std::cout << "capacity:" << v.capacity() << std::endl; // output 0
50
4.1 Linear Container CHAPTER 04 CONTAINERS
// Additional memory can be returned to the system via the shrink_to_fit() call
v.shrink_to_fit();
std::cout << "size:" << v.size() << std::endl; // output 0
std::cout << "capacity:" << v.capacity() << std::endl; // output 0
The second problem is much simpler. Using std::array can make the code more “modern” and
encapsulate some manipulation unctions, such as getting the array size and checking i it is not empty,
and also using the standard riendly. Container algorithms in the library, such as std::sort.
// iterator support
for (auto &i : arr)
{
// ...
}
51
4.1 Linear Container CHAPTER 04 CONTAINERS
When we started using std::array, it was inevitable that we would encounter a C-style compatible
interace. There are three ways to do this:
// use `std::sort`
std::sort(arr.begin(), arr.end());
std::forward_list
std::forward_list is a list container, and the usage is similar to std::list, so we don’t spend a
lot o time introducing it.
Need to know is that, unlike the implementation o the doubly linked list o std::list,
std::forward_list is implemented using a singly linked list. Provides element insertion o O(1)
complexity, does not support ast random access (this is also a eature o linked lists), It is also the only
container in the standard library container that does not provide the size() method. Has a higher
space utilization than std::list when bidirectional iteration is not required.
52
4.2 Unordered Container CHAPTER 04 CONTAINERS
We are already amiliar with the ordered container std::map/std::set in traditional C++. These
elements are internally implemented by red-black trees. The average complexity o inserts and searches
is O(log(size)). When inserting an element, the element size is compared according to the < operator
and the element is determined to be the same. And select the appropriate location to insert into the
container. When traversing the elements in this container, the output will be traversed one by one in
the order o the < operator.
The elements in the unordered container are not sorted, and the internals is implemented by the
Hash table. The average complexity o inserting and searching or elements is O(constant), Signicant
perormance gains can be achieved without concern or the order o the elements inside the container.
#include <iostream>
#include <string>
#include <unordered_map>
#include <map>
int main() {
// initialized in same order
std::unordered_map<int, std::string> u = {
{1, "1"},
{3, "3"},
{2, "2"}
};
std::map<int, std::string> v = {
{1, "1"},
{3, "3"},
{2, "2"}
};
53
4.3 Tuples CHAPTER 04 CONTAINERS
std::unordered_map
Key:[2] Value:[2]
Key:[3] Value:[3]
Key:[1] Value:[1]
std::map
Key:[1] Value:[1]
Key:[2] Value:[2]
Key:[3] Value:[3]
4.3 Tuples
Programmers who have known Python should be aware o the concept o tuples. Looking at the
containers in traditional C++, except or std::pair there seems to be no ready-made structure to store
dierent types o data (usually we will dene the structure ourselves). But the faw o std::pair is
obvious, only two elements can be saved.
Basic Operations
#include <tuple>
#include <iostream>
54
4.3 Tuples CHAPTER 04 CONTAINERS
int main() {
auto student = get_student(0);
std::cout << "ID: 0, "
<< "GPA: " << std::get<0>(student) << ", "
<< "Grade: " << std::get<1>(student) << ", "
<< "Name: " << std::get<2>(student) << '\n';
double gpa;
char grade;
std::string name;
// unpack tuples
std::tie(gpa, grade, name) = get_student(1);
std::cout << "ID: 1, "
<< "GPA: " << gpa << ", "
<< "Grade: " << grade << ", "
<< "Name: " << name << '\n';
}
std::get In addition to using constants to get tuple objects, C++14 adds usage types to get objects
in tuples:
Runtime Indexing
I you think about it, you might nd the problem with the above code. std::get<> depends on a
compile-time constant, so the ollowing is not legal:
int index = 1;
std::get<index>(t);
So what do you do? The answer is to use std::variant<> (introduced by C++ 17) to provide
55
4.3 Tuples CHAPTER 04 CONTAINERS
type template parameters or variant<> You can have a variant<> to accommodate several types o
variables provided (in other languages, such as Python/JavaScript, etc., as dynamic types):
#include <variant>
template <size_t n, typename... T>
constexpr std::variant<T...> _tuple_index(const std::tuple<T...>& tpl, size_t i) {
if constexpr (n >= sizeof...(T))
throw std::out_of_range(" .");
if (i == n)
return std::variant<T...>{ std::in_place_index<n>, std::get<n>(tpl) };
return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(tpl, i);
}
template <typename... T>
constexpr std::variant<T...> tuple_index(const std::tuple<T...>& tpl, size_t i) {
return _tuple_index<0>(tpl, i);
}
template <typename T0, typename ... Ts>
std::ostream & operator<< (std::ostream & s, std::variant<T0, Ts...> const & v) {
std::visit([&](auto && x){ s << x;}, v);
return s;
}
So we can:
int i = 1;
std::cout << tuple_index(t, i) << std::endl;
Another common requirement is to merge two tuples, which can be done with std::tuple_cat:
You can immediately see how quickly you can traverse a tuple? But we just introduced how to index
a tuple by a very number at runtime, then the traversal becomes simpler. First, we need to know the
length o a tuple, which can:
56
Conclusion CHAPTER 05 SMART POINTERS AND MEMORY MANAGEMENT
Conclusion
This chapter briefy introduces the new containers in modern C++. Their usage is similar to that
o the existing containers in C++. It is relatively simple, and you can choose the containers you need to
use according to the actual scene, to get better perormance.
Although std::tuple is eective, the standard library provides limited unctionality and there is
no way to meet the requirements o runtime indexing and iteration. Fortunately, we have other methods
that we can implement on our own.
In traditional C++, “remembering” to manually release resources is not always a best practice.
Because we are likely to orget to release resources and lead to leakage. So the usual practice is that or
an object, we apply or space when constructor, and ree space when the destructor (called when leaving
the scope). That is, we oten say that the RAII resource acquisition is the initialization technology.
There are exceptions to everything, we always need to allocate objects on ree storage. In
traditional C++ we have to use new and delete to “remember” to release resources. C++11
introduces the concept o smart pointers, using the idea o reerence counting so that program-
mers no longer need to care about manually releasing memory. These smart pointers include
std::shared_ptr/std::unique_ptr/std::weak_ptr, which need to include the header le <memory>.
Note: The reerence count is not garbage collection. The reerence count can recover the
objects that are no longer used as soon as possible, and will not cause long waits during the
recycling process. More clearly and indicate the lie cycle o resources.
57
5.2 std::shared_ptr CHAPTER 05 SMART POINTERS AND MEMORY MANAGEMENT
5.2 std::shared_ptr
std::shared_ptr is a smart pointer that records how many shared_ptr points to an object, elimi-
nating to call delete, which automatically deletes the object when the reerence count becomes zero.
But not enough, because using std::shared_ptr still needs to be called with new, which makes the
code a certain degree o asymmetry.
std::make_shared can be used to eliminate the explicit use o new, so std::make_shared will
allocate the objects in the generated parameters. And return the std::shared_ptr pointer o this
object type. For example:
#include <iostream>
#include <memory>
void foo(std::shared_ptr<int> i) {
(*i)++;
}
int main() {
// auto pointer = new int(10); // illegal, no direct assignment
// Constructed a std::shared_ptr
auto pointer = std::make_shared<int>(10);
foo(pointer);
std::cout << *pointer << std::endl; // 11
// The shared_ptr will be destructed before leaving the scope
return 0;
}
std::shared_ptr can get the raw pointer through the get() method and reduce the reerence count
by reset(). And see the reerence count o an object by use_count(). E.g:
pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
58
5.3 std::unique_ptr CHAPTER 05 SMART POINTERS AND MEMORY MANAGEMENT
pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
5.3 std::unique_ptr
std::unique_ptr is an exclusive smart pointer that prohibits other smart pointers rom sharing
the same object, thus keeping the code sae:
make_unique is not complicated. C++11 does not provide std::make_unique, which can be
implemented by itsel:
As or why it wasn’t provided, Herb Sutter, chairman o the C++ Standards Committee,
mentioned in his blog that it was because they were orgotten.
Since it is monopolized, in other words, it cannot be copied. However, we can use std::move to
transer it to other unique_ptr, or example:
#include <iostream>
#include <memory>
struct Foo {
Foo() { std::cout << "Foo::Foo" << std::endl; }
~Foo() { std::cout << "Foo::~Foo" << std::endl; }
void foo() { std::cout << "Foo::foo" << std::endl; }
};
59
5.4 std::weak_ptr CHAPTER 05 SMART POINTERS AND MEMORY MANAGEMENT
int main() {
std::unique_ptr<Foo> p1(std::make_unique<Foo>());
// p1 is empty, no prints
if(p1) p1->foo();
p1 = std::move(p2);
// p2 is empty, no prints
if(p2) p2->foo();
std::cout << "p2 was destroyed" << std::endl;
}
// p1 is not empty, prints
if (p1) p1->foo();
5.4 std::weak_ptr
I you think about std::shared_ptr careully, you will still nd that there is still a problem that
resources cannot be released. Look at the ollowing example:
#include <iostream>
#include <memory>
class A;
60
5.4 std::weak_ptr CHAPTER 05 SMART POINTERS AND MEMORY MANAGEMENT
class B;
class A {
public:
std::shared_ptr<B> pointer;
~A() {
std::cout << "A was destroyed" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> pointer;
~B() {
std::cout << "B was destroyed" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->pointer = b;
b->pointer = a;
return 0;
}
The result is that A and B will not be destroyed. This is because the pointer inside a, b also
reerences a, b, which makes the reerence count o a, b becomes 2, leaving the scope. When the a,
b smart pointer is destructed, it can only cause the reerence count o this area to be decremented by
one. This causes the memory area reerence count pointed to by the a, b object to be non-zero, but the
external has no way to nd this area, it also caused a memory leak, as shown in Figure 5.1:
The solution to this problem is to use the weak reerence pointer std::weak_ptr, which is a weak
reerence (compared to std::shared_ptr is a strong reerence). A weak reerence does not cause an
61
Conclusion CHAPTER 05 SMART POINTERS AND MEMORY MANAGEMENT
increase in the reerence count. When a weak reerence is used, the nal release process is shown in
Figure 5.2:
In the above gure, only B is let in the last step, and B does not have any smart pointers to
reerence it, so this memory resource will also be released.
std::weak_ptr has no implemented * and -> operators, thereore it cannot operate on resources.
std::weak_ptr allows us to check i a std::shared_ptr exists or not. The expired() method o a
std::weak_ptr returns false when the resource is not released; Otherwise, it returns true. Furthermore,
it can also be used or the purpose o obtaining std::shared_ptr, which points to the original object.
The lock() method returns a std::shared_ptr to the original object when the resource is not released,
or nullptr otherwise.
Conclusion
The technology o smart pointers is not novel. It is a common technology in many languages.
Modern C++ introduces this technology, which eliminates the abuse o new/delete to a certain extent.
It is a more mature technology. Programming paradigm.
Further Readings
62
CHAPTER 06 REGULAR EXPRESSION
6.1 Introduction
Regular expressions are not part o the C++ language and thereore we only briefy introduced it
here.
Regular expressions describe a pattern o string matching. The general use o regular expressions is
mainly to achieve the ollowing three requirements:
Regular expressions are text patterns consisting o ordinary characters (such as a to z) and special
characters. A pattern describes one or more strings to match when searching or text. Regular expressions
act as a template to match a character pattern to the string being searched.
Ordinary characters
Normal characters include all printable and unprintable characters that are not explicitly specied
as metacharacters. This includes all uppercase and lowercase letters, all numbers, all punctuation, and
some other symbols.
Special characters
A special character is a character with special meaning in a regular expression and is also the core
matching syntax o a regular expression. See the table below:
Symbol Description
63
6.1 Introduction CHAPTER 06 REGULAR EXPRESSION
Symbol Description
* Matches the
previous
subexpression
zero or more
times.
+ Matches the
previous
subexpression
one or more
times.
. Matches any
single character
except the
newline character
\n.
[ Marks the
beginning o a
bracket
expression.
? Matches the
previous
subexpression
zero or one time,
or indicates a
non-greedy
qualier.
64
6.1 Introduction CHAPTER 06 REGULAR EXPRESSION
Symbol Description
65
6.1 Introduction CHAPTER 06 REGULAR EXPRESSION
Quantifers
The qualier is used to speciy how many times a given component o a regular expression must
appear to satisy the match. See the table below:
Symbol Description
With these two tables, we can usually read almost all regular expressions.
66
6.2 std::regex and Its Related CHAPTER 06 REGULAR EXPRESSION
The most common way to match string content is to use regular expressions. Unortunately, in
traditional C++, regular expressions have not been supported by the language level, and are not included
in the standard library. C++ is a high-perormance language. In the development o background services,
the use o regular expressions is also used when judging URL resource links. The most mature and
common practice in the industry.
The general solution is to use the regular expression library o boost. C++11 ocially incorporates
the processing o regular expressions into the standard library, providing standard support rom the
language level and no longer relying on third parties.
The regular expression library provided by C++11 operates on the std::string object, and the
pattern std::regex (essentially std::basic_regex) is initialized and matched by std::regex_match
Produces std::smatch (essentially the std::match_results object).
We use a simple example to briefy introduce the use o this library. Consider the ollowing regular
expression:
• [az]+\.txt: In this regular expression, [az] means matching a lowercase letter, + can match the
previous expression multiple times, so [az]+ can Matches a string o lowercase letters. In the
regular expression, a . means to match any character, and \. means to match the character .,
and the last txt means to match txt exactly three letters. So the content o this regular expression
to match is a text le consisting o pure lowercase letters.
std::regex_match is used to match strings and regular expressions, and there are many dierent
overloaded orms. The simplest orm is to pass std::string and a std::regex to match. When the
match is successul, it will return true, otherwise, it will return false. For example:
#include <iostream>
#include <string>
#include <regex>
int main() {
std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};
// In C++, `\` will be used as an escape character in the string.
// In order for `\.` to be passed as a regular expression,
// it is necessary to perform second escaping of `\`, thus we have `\\.`
std::regex txt_regex("[a-z]+\\.txt");
for (const auto &fname: fnames)
std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
}
67
Conclusion CHAPTER 06 REGULAR EXPRESSION
std::regex base_regex("([a-z]+)\\.txt");
std::smatch base_match;
for(const auto &fname: fnames) {
if (std::regex_match(fname, base_match, base_regex)) {
// the first element of std::smatch matches the entire string
// the second element of std::smatch matches the first expression
// with brackets
if (base_match.size() == 2) {
std::string base = base_match[1].str();
std::cout << "sub-match[0]: " << base_match[0].str() << std::endl;
std::cout << fname << " sub-match[1]: " << base << std::endl;
}
}
}
foo.txt: 1
bar.txt: 1
test: 0
a0.txt: 0
AAA.txt: 0
sub-match[0]: foo.txt
foo.txt sub-match[1]: foo
sub-match[0]: bar.txt
bar.txt sub-match[1]: bar
Conclusion
This section briefy introduces the regular expression itsel, and then introduces the use o the
regular expression library through a practical example based on the main requirements o using regular
expressions.
Exercise
In web server development, we usually want to serve some routes that satisy a certain condition.
Regular expressions are one o the tools to accomplish this. Given the ollowing request structure:
68
Exercise CHAPTER 06 REGULAR EXPRESSION
struct Request {
// request method, POST, GET; path; HTTP version
std::string method, path, http_version;
// use smart pointer for reference counting of content
std::shared_ptr<std::istream> content;
// hash container, key-value dict
std::unordered_map<std::string, std::string> header;
// use regular expression for path match
std::smatch path_match;
};
typedef std::map<
std::string, std::unordered_map<
std::string,std::function<void(std::ostream&, Request&)>>> resource_type;
void start() {
// TODO
}
protected:
Request parse_request(std::istream& stream) const {
// TODO
}
}
Please implement the member unctions start() and parse_request. Enable server template users
to speciy routes as ollows:
template<typename SERVER_TYPE>
void start_server(SERVER_TYPE &server) {
69
Further Readings CHAPTER 06 REGULAR EXPRESSION
server.resource["fill_your_reg_ex"]["GET"] =
[](ostream& response, Request& request)
{
string number=request.path_match[1];
response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length()
<< "\r\n\r\n" << number;
};
// (...)
};
server.start();
}
Further Readings
70
CHAPTER 07 PARALLELISM AND CONCURRENCY
std::thread is used to create an execution thread instance, so it is the basis or all concurrent
programming. It needs to include the <thread> header le when using it. It provides a number o basic
thread operations, such as get_id() to get the thread ID o the thread being created, use join() to
join a thread, etc., or example:
#include <iostream>
#include <thread>
int main() {
std::thread t([](){
std::cout << "hello world." << std::endl;
});
t.join();
return 0;
}
We have already learned the basics o concurrency technology in the operating system, or the
database, and mutex is one o the cores. C++11 introduces a class related to mutex, with all related
unctions in the <mutex> header le.
std::mutex is the most basic mutex class in C++11, and you can create a mutex by instantiating
std::mutex. It can be locked by its member unction lock(), and unlock() can be unlocked. But in
the process o actually writing the code, it is best not to directly call the member unction, Because
calling member unctions, you need to call unlock() at the exit o each critical section, and o course,
exceptions. At this time, C++11 also provides a template class std::lock_guard or the RAII syntax
or the mutex.
RAII guarantees the exceptional security o the code while keeping the simplicity o the code.
#include <iostream>
#include <mutex>
#include <thread>
int v = 1;
71
7.2 Mutex and Critical Section CHAPTER 07 PARALLELISM AND CONCURRENCY
std::lock_guard<std::mutex> lock(mtx);
int main() {
std::thread t1(critical_section, 2), t2(critical_section, 3);
t1.join();
t2.join();
Because C++ guarantees that all stack objects will be destroyed at the end o the declaration period,
such code is also extremely sae. Whether critical_section() returns normally or i an exception is
thrown in the middle, a stack rollback is thrown, and unlock() is automatically called.
std::lock_guard cannot explicitly call lock and unlock, and std::unique_lock can be called
anywhere ater the declaration. It can reduce the scope o the lock and provide higher concurrency.
I you use the condition variable std::condition_variable::wait you must use std::unique_lock
as a parameter.
For instance:
#include <iostream>
#include <mutex>
#include <thread>
int v = 1;
72
7.3 Future CHAPTER 07 PARALLELISM AND CONCURRENCY
v = change_v;
std::cout << v << std::endl;
// release the lock
lock.unlock();
int main() {
std::thread t1(critical_section, 2), t2(critical_section, 3);
t1.join();
t2.join();
return 0;
}
7.3 Future
The Future is represented by std::future, which provides a way to access the results o asyn-
chronous operations. This sentence is very dicult to understand. To understand this eature, we need
to understand the multi-threaded behavior beore C++11.
Imagine i our main thread A wants to open a new thread B to perorm some o our expected tasks
and return me a result. At this time, thread A may be busy with other things and have no time to take
into account the results o B. So we naturally hope to get the result o thread B at a certain time.
Beore the introduction o std::future in C++11, the usual practice is: Create a thread A, start
task B in thread A, send an event when it is ready, and save the result in a global variable. The main
unction thread A is doing other things. When the result is needed, a thread is called to wait or the
unction to get the result o the execution.
The std::future provided by C++11 simplies this process and can be used to get the results o
asynchronous tasks. Naturally, we can easily imagine it as a simple means o thread synchronization,
namely the barrier.
To see an example, we use extra std::packaged_task, which can be used to wrap any target that
can be called or asynchronous calls. For example:
73
7.4 Condition Variable CHAPTER 07 PARALLELISM AND CONCURRENCY
#include <iostream>
#include <thread>
#include <future>
int main() {
// pack a lambda expression that returns 7 into a std::packaged_task
std::packaged_task<int()> task([](){return 7;});
// get the future of task
std::future<int> result = task.get_future(); // run task in a thread
std::thread(std::move(task)).detach();
std::cout << "waiting...";
result.wait(); // block until future has arrived
// output result
std::cout << "done!" << std:: endl << "future result is "
<< result.get() << std::endl;
return 0;
}
Ater encapsulating the target to be called, you can use get_future() to get a std::future object
to implement thread synchronization later.
The condition variable std::condition_variable was born to solve the deadlock and was intro-
duced when the mutex operation was not enough. For example, a thread may need to wait or a condition
to be true to continue execution. A dead wait loop can cause all other threads to ail to enter the criti-
cal section so that when the condition is true, a deadlock occurs. Thereore, the condition_variable
instance is created primarily to wake up the waiting thread and avoid deadlocks. notify_one() o
std::condition_variable is used to wake up a thread; notify_all() is to notiy all threads. Below
is an example o a producer and consumer model:
#include <queue>
#include <chrono>
#include <mutex>
#include <thread>
#include <iostream>
#include <condition_variable>
int main() {
std::queue<int> produced_nums;
std::mutex mtx;
74
7.4 Condition Variable CHAPTER 07 PARALLELISM AND CONCURRENCY
std::condition_variable cv;
bool notified = false; // notification sign
std::thread p(producer);
std::thread cs[2];
for (int i = 0; i < 2; ++i) {
cs[i] = std::thread(consumer);
}
p.join();
for (int i = 0; i < 2; ++i) {
cs[i].join();
75
7.5 Atomic Operation and Memory Model CHAPTER 07 PARALLELISM AND CONCURRENCY
}
return 0;
}
It is worth mentioning that although we can use notify_one() in the producer, it is not recom-
mended to use it here. Because in the case o multiple consumers, our consumer implementation simply
gives up the lock holding, which makes it possible or other consumers to compete or this lock, to bet-
ter utilize the concurrency between multiple consumers. Having said that, but in act because o the
exclusivity o std::mutex, We simply can’t expect multiple consumers to be able to produce content in
a parallel consumer queue, and we still need a more granular approach.
Careul readers may be tempted by the act that the example o the producer-consumer model in the
previous section may have compiler optimizations that cause program errors. For example, the boolean
notified is not modied by volatile, and the compiler may have optimizations or this variable, such
as the value o a register. As a result, the consumer thread can never observe the change o this value.
This is a good question. To explain this problem, we need to urther discuss the concept o the memory
model introduced rom C++11. Let’s rst look at a question. What is the output o the ollowing code?
#include <thread>
#include <iostream>
int main() {
int a = 0;
volatile int flag = 0;
std::thread t1([&]() {
while (flag != 1);
int b = a;
std::cout << "b = " << b << std::endl;
});
std::thread t2([&]() {
a = 5;
flag = 1;
});
t1.join();
t2.join();
return 0;
76
7.5 Atomic Operation and Memory Model CHAPTER 07 PARALLELISM AND CONCURRENCY
Intuitively, it seems that a = 5; in t2 always executes beore flag = 1; and while (flag != 1)
in t1. It looks like there is a guarantee the line std ::cout << "b = " << b << std::endl; will not
be executed beore the mark is changed. Logically, it seems that the value o b should be equal to 5. But
the actual situation is much more complicated than this, or the code itsel is undened behavior because,
or a and flag, they are read and written in two parallel threads. There has been competition. Also,
even i we ignore competing or reading and writing, it is still possible to receive out-o-order execution
o the CPU and the impact o the compiler on the rearrangement o instructions. Cause a = 5 to occur
ater flag = 1. Thus b may output 0.
Atomic Operation
std::mutex can solve the problem o concurrent read and write, but the mutex is an operating
system-level unction. This is because the implementation o a mutex usually contains two basic princi-
ples:
1. Provide automatic state transition between threads, that is, “lock” state
2. Ensure that the memory o the manipulated variable is isolated rom the critical section during the
mutex operation
This is a very strong set o synchronization conditions, in other words when it is nally compiled
into a CPU instruction, it will behave like a lot o instructions (we will look at how to implement a simple
mutex later). This seems too harsh or a variable that requires only atomic operations (no intermediate
state).
The research on synchronization conditions has a very long history, and we will not go into details
here. Readers should understand that under the modern CPU architecture, atomic operations at the
CPU instruction level are provided. Thereore, in the C++11 multi-threaded shared variable reading
and writing, the introduction o the std::atomic template, so that we instantiate an atomic type, will
be an Atomic type read and write operations are minimized rom a set o instructions to a single CPU
instruction. E.g:
std::atomic<int> counter;
And provides basic numeric member unctions or atomic types o integers or foating-point numbers,
or example, Including fetch_add, fetch_sub, etc., and the corresponding +, - version is provided by
overload. For example, the ollowing example:
#include <atomic>
#include <thread>
#include <iostream>
77
7.5 Atomic Operation and Memory Model CHAPTER 07 PARALLELISM AND CONCURRENCY
int main() {
std::thread t1([](){
count.fetch_add(1);
});
std::thread t2([](){
count++; // identical to fetch_add
count += 1; // identical to fetch_add
});
t1.join();
t2.join();
std::cout << count << std::endl;
return 0;
}
O course, not all types provide atomic operations because the easibility o atomic operations de-
pends on the architecture o the CPU and whether the type structure being instantiated satises the mem-
ory alignment requirements o the architecture, so we can always pass std::atomic<T>::is_lock_free
to check i the atom type needs to support atomic operations, or example:
#include <atomic>
#include <iostream>
struct A {
float x;
int y;
long long z;
};
int main() {
std::atomic<A> a;
std::cout << std::boolalpha << a.is_lock_free() << std::endl;
return 0;
}
Consistency Model
Multiple threads executing in parallel, discussed at some macro level, can be roughly considered a
distributed system. In a distributed system, any communication or even local operation takes a certain
amount o time, and even unreliable communication occurs.
I we orce the operation o a variable v between multiple threads to be atomic, that is, any thread
78
7.5 Atomic Operation and Memory Model CHAPTER 07 PARALLELISM AND CONCURRENCY
ater the operation o v Other threads can synchronize to perceive changes in v, or the variable v,
which appears as a sequential execution o the program, it does not have any eciency gains due to the
introduction o multithreading. Is there any way to accelerate this properly? The answer is to weaken
the synchronization conditions between processes in atomic operations.
In principle, each thread can correspond to a cluster node, and communication between threads is
almost equivalent to communication between cluster nodes. Weakening the synchronization conditions
between processes, usually we will consider our dierent consistency models:
1. Linear consistency: Also known as strong consistency or atomic consistency. It requires that any
read operation can read the most recent write o a certain data, and the order o operation o all
threads is consistent with the order under the global clock.
x.store(1) x.load()
T1 ---------+----------------+------>
T2 -------------------+------------->
x.store(2)
In this case, thread T1, T2 is twice atomic to x, and x.store(1) is strictly beore x.store(2).
x.store(2) strictly occurs beore x.load(). It is worth mentioning that linear consistency re-
quirements or global clocks are dicult to achieve, which is why people continue to study other
consistent algorithms under this weaker consistency.
2. Sequential consistency: It is also required that any read operation can read the last data written
by the data, but it is not required to be consistent with the order o the global clock.
T2 ---------------+---------------------->
x.store(2)
or
T2 ------+------------------------------->
x.store(2)
79
7.5 Atomic Operation and Memory Model CHAPTER 07 PARALLELISM AND CONCURRENCY
Under the order consistency requirement, x.load() must read the last written data, so
x.store(2) and x.store(1) do not have any guarantees, as long as x.store(2) o T2 occurs
beore x.store(3).
3. Causal consistency: its requirements are urther reduced, only the sequence o causal operations is
guaranteed, and the order o non-causal operations is not required.
a = 1 b = 2
T1 ----+-----------+---------------------------->
T2 ------+--------------------+--------+-------->
x.store(3) c = a + b y.load()
or
a = 1 b = 2
T1 ----+-----------+---------------------------->
T2 ------+--------------------+--------+-------->
x.store(3) y.load() c = a + b
or
b = 2 a = 1
T1 ----+-----------+---------------------------->
T2 ------+--------------------+--------+-------->
y.load() c = a + b x.store(3)
The three examples given above are all causal consistent because, in the whole process, only c has
a dependency on a and b, and x and y are not related in this example. (But in actual situations
we need more detailed inormation to determine that x is not related to y)
4. Final Consistency: It is the weakest consistency requirement. It only guarantees that an operation
will be observed at a certain point in the uture, but does not require the observed time. So we
can even strengthen this condition a bit, or example, to speciy that the time observed or an
operation is always bounded. O course, this is no longer within our discussion.
x.store(3) x.store(4)
T1 ----+-----------+-------------------------------------------->
80
7.5 Atomic Operation and Memory Model CHAPTER 07 PARALLELISM AND CONCURRENCY
T2 ---------+------------+--------------------+--------+-------->
x.read() x.read() x.read() x.read()
In the above case, i we assume that the initial value o x is 0, then the our times ‘x.read() in T2
may be but not limited to the ollowing:
Memory Orders
To achieve the ultimate perormance and achieve consistency o various strength requirements,
C++11 denes six dierent memory sequences or atomic operations. The option std::memory_order
expresses our synchronization models between multiple threads:
1. Relaxed model: Under this model, atomic operations within a single thread are executed sequen-
tially, and instruction reordering is not allowed, but the order o atomic operations between dier-
ent threads is arbitrary. The type is specied by std::memory_order_relaxed. Let’s look at an
example:
2. Release/consumption model: In this model, we begin to limit the order o operations between pro-
cesses. I a thread needs to modiy a value, but another thread will have a dependency on that op-
eration o the value, that is, the latter depends on the ormer. Specically, thread A has completed
three writes to x, and thread B relies only on the third x write operation, regardless o the rst two
81
7.5 Atomic Operation and Memory Model CHAPTER 07 PARALLELISM AND CONCURRENCY
3. Release/Acquire model: Under this model, we can urther tighten the order o atomic operations
between dierent threads, speciying the timing between releasing std::memory_order_release
and getting std::memory_order_acquire. All write operations beore the release operation is
visible to any other thread, i.e., happens beore.
As you can see, std::memory_order_release ensures that a write beore a release does not occur
ater the release operation, which is a backward barrier, and std::memory_order_acquire en-
sures that a subsequent read or write ater a acquire does not occur beore the acquire operation,
which is a orward barrier. For the std::memory_order_acq_rel option, combines the charac-
teristics o the two barriers and determines a unique memory barrier, such that reads and writes
o the current thread will not be rearranged across the barrier.
std::vector<int> v;
std::atomic<int> flag = {0};
std::thread release([&]() {
v.push_back(42);
flag.store(1, std::memory_order_release);
});
std::thread acqrel([&]() {
int expected = 1; // must before compare_exchange_strong
while(!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel))
82
7.5 Atomic Operation and Memory Model CHAPTER 07 PARALLELISM AND CONCURRENCY
4. Sequential Consistent Model: Under this model, atomic operations satisy sequence con-
sistency, which in turn can cause perormance loss. It can be specied explicitly by
std::memory_order_seq_cst. Let’s look at a nal example:
This example is essentially the same as the rst loose model example. Just change the memory
order o the atomic operation to memory_order_seq_cst. Interested readers can write their own
programs to measure the perormance dierence caused by these two dierent memory sequences.
83
Conclusion CHAPTER 08 FILE SYSTEM
Conclusion
The C++11 language layer provides support or concurrent programming. This section briefy intro-
duces std::thread/std::mutex/std::future, an important tool that can’t be avoided in concurrent
programming. In addition, we also introduced the “memory model” as one o the most important ea-
tures o C++11. They provide a critical oundation or standardized high-perormance computing or
C++.
Exercises
Further Readings
The le system library provides unctions related to the operation o the le system, path, regular
les, directories, and so on. Similar to the regular expression library, it was one o the rst libraries to
be launched by boost and eventually merged into the C++ standard.
TODO:
84
8.2 std::lesystem CHAPTER 09 MINOR FEATURES
8.2 std::lesystem
TODO:
Further Readings
long long int is not the rst to be introduced in C++11. As early as C99, long long int has
been included in the C standard, so most compilers already support it. C++11 now ormally incorporate
it into the standard library, speciying a long long int type with at least 64 bits.
One o the big advantages o C++ over C is that C++ itsel denes a complete set o exception
handling mechanisms. However, beore C++11, almost no one used to write an exception declaration
expression ater the unction name. Starting rom C++11, this mechanism was deprecated, so we will
not discuss or introduce the previous mechanism. How to work and how to use it, you should not take
the initiative to understand it.
I a unction modied with noexcept is thrown, the compiler will use std::terminate() to imme-
diately terminate the program.
noexcept can also be used as an operator to manipulate an expression. When the expression has
no exception, it returns true, otherwise, it returns false.
#include <iostream>
void may_throw() {
throw true;
85
9.2 noexcept and Its Operations CHAPTER 09 MINOR FEATURES
}
auto non_block_throw = []{
may_throw();
};
void no_throw() noexcept {
return;
}
noexcept can modiy the unction o blocking exceptions ater modiying a unction. I an exception
is generated internally, the external will not trigger. For instance:
try {
may_throw();
} catch (...) {
std::cout << "exception captured from may_throw()" << std::endl;
}
try {
non_block_throw();
} catch (...) {
std::cout << "exception captured from non_block_throw()" << std::endl;
}
try {
block_throw();
} catch (...) {
std::cout << "exception captured from block_throw()" << std::endl;
}
86
9.3 Literal CHAPTER 09 MINOR FEATURES
9.3 Literal
In traditional C++, it is very painul to write a string ull o special characters. For example, a
string containing HTML ontology needs to add a large number o escape characters. For example, a le
path on Windows oten as: C:\\Path\\To\\File.
C++11 provides the original string literals, which can be decorated with R in ront o a string, and
the original string is wrapped in parentheses, or example:
#include <iostream>
#include <string>
int main() {
std::string str = R"(C:\Path\To\File)";
std::cout << str << std::endl;
return 0;
}
Custom Literal
C++11 introduces the ability to customize literals by overloading the double quotes sux operator:
int main() {
auto str = "abc"_wow1;
auto num = 1_wow2;
std::cout << str << std::endl;
std::cout << num << std::endl;
return 0;
}
87
9.4 Memory Alignment CHAPTER 09 MINOR FEATURES
1. Integer literal: When overloading, you must use unsigned long long, const char *, and tem-
plate literal operator parameters. The ormer is used in the above code;
2. Floating-point literals: You must use long double, const char *, and template literals when
overloading;
3. String literals: A parameter table o the orm (const char *, size_t) must be used;
4. Character literals: Parameters can only be char, wchar_t, char16_t, char32_t.
C++ 11 introduces two new keywords, alignof and alignas, to support control o memory align-
ment. The alignof keyword can get a platorm-dependent value o type std::size_t to query the
alignment o the platorm. O course, we are sometimes not satised with this, and even want to cus-
tomize the alignment o the structure. Similarly, C++ 11 introduces alignas. To reshape the alignment
o a structure. Let’s look at two examples:
#include <iostream>
struct Storage {
char a;
int b;
double c;
long long d;
};
int main() {
std::cout << alignof(Storage) << std::endl;
std::cout << alignof(AlignasStorage) << std::endl;
return 0;
}
where std::max_align_t requires the same alignment or each scalar type, so it has almost no
dierence in maximum scalars. In turn, the result on most platorms is long double, so the alignment
requirement or AlignasStorage we get here is 8 or 16.
88
Conclusion CHAPTER 10 OUTLOOK: INTRODUCTION OF C++20
Conclusion
Several o the eatures introduced in this section are those that use more requent eatures rom
modern C++ eatures that have not yet been introduced. noexcept is the most important eature. One
o its eatures is to prevent the spread o anomalies, eective Let the compiler optimize our code to the
maximum extent possible.
C++20 seems to be an exciting update. For example, as early as C++11, the Concept, which was
eager to call or high-altitude but ultimately lost, is now on the line. The C++ Organizing Committee de-
cided to vote to nalize C++20 with many proposals, such as Concepts/Module/Coroutine/Ranges/
and so on. In this chapter, we’ll take a look at some o the important eatures that C++20 will introduce.
Concept
The concept is a urther enhancement to C++ template programming. In simple terms, the concept
is a compile-time eature. It allows the compiler to evaluate template parameters at compile-time, greatly
enhancing our experience with template programming in C++. When programming with templates, we
oten encounter a variety o heinous errors. This is because we have so ar been unable to check and limit
template parameters. For example, the ollowing two lines o code can cause a lot o almost unreadable
compilation errors:
#include <list>
#include <algorithm>
int main() {
std::list<int> l = {1, 2, 3};
std::sort(l.begin(), l.end());
return 0;
}
The root cause o this code error is that std::sort must provide a random iterator or the sorting
container, otherwise it will not be used, and we know that std::list does not support random access.
In the conceptual language, the iterator in std::list does not satisy the constraint o the concept o
random iterators in std::sort. Ater introducing the concept, we can constrain the template parameters
like this:
abbreviate as:
89
Module CHAPTER 10 OUTLOOK: INTRODUCTION OF C++20
TODO:
Module
TODO:
Contract
TODO:
Range
TODO:
Coroutine
TODO:
Conclusion
In general, I nally saw the exciting eatures o Concepts/Ranges/Modules in C++20. This is still
ull o charm or a programming language that is already in its thirties.
Further Readings
90
APPENDIX 2: MODERN C++ BEST PRACTICES
First o all, congratulations on reading this book! I hope this book has raised your interest in
modern C++.
As mentioned in the introduction to this book, this book is just a book that takes you quickly to the
new eatures o modern C++ 11/14/17/20, rather than the advanced learning practice o C++ “Black
Magic”. The author o course also thinks about this demand, but the content is very dicult and there
are ew audiences. Here, the author lists some materials that can help you learn more about modern
C++ based on this book. I hope I can help you:
• C++ Reerence
• CppCon YouTube Channel
• Ulrich Drepper. What Every Programmer Should Know About Memory. 2007
• to be added
In this appendix we will briefy talk about the best practices o modern C++. In general, the
author’s thoughts on C++’s best practices are mainly absorbed rom Eective Modern C++ and C++
Style Guide. In this appendix, we will briefy discuss and use the actual examples to illustrate the
methods, and introduce some o the author’s personal, non-common, non-sensible best practices,
and how to ensure the overall quality o the code.
Common Tools
TODO:
Coding Style
TODO:
Overall Perormance
TODO:
Code Security
TODO:
91
Maintainability APPENDIX 2: MODERN C++ BEST PRACTICES
Maintainability
TODO:
Portability
TODO:
92