book: typo and grammar fixes (#185)

This commit is contained in:
Nikhil Garuda
2021-04-18 15:42:26 +05:30
committed by GitHub
parent 45bef893a7
commit 731a219d89
10 changed files with 421 additions and 429 deletions

View File

@@ -13,33 +13,33 @@ order: 0
C++ user group is fairly large. From the advent of C++98 to the official finalization of C++11, it has accumulated over a decade. C++14/17 is an important complement and optimization for C++11, and C++20 brings this language to the door of modernization. The extended features of all these new standards are given to the C++ language. Infused with new vitality.
C++ programmers, who are still using **traditional C++** (this book refers to C++98 and its previous C++ standards as traditional C++), may even amazed by the fact that they are not using the same language while reading modern C++ code.
**Modern C++** (this book refers to C++11/14/17/20) introduces a lot of features into traditional C++, which makes the whole C++ become language that modernized. Modern C++ not only enhances the usability of the C++ language itself, but the modification of the `auto` keyword semantics gives us more confidence in manipulating extremely complex template types. At the same time, a lot of enhancements have been made to the language runtime. The emergence of Lambda expressions has made C++ have the "closure" feature of "anonymous functions", which is almost in modern programming languages (such as Python/Swift/.. It has become commonplace, and the emergence of rvalue references has solved the problem of temporary object efficiency that C++ has long been criticized.
**Modern C++** (this book refers to C++11/14/17/20) introduces a lot of features into traditional C++, which makes the whole C++ become a language that modernized. Modern C++ not only enhances the usability of the C++ language itself, but the modification of the `auto` keyword semantics gives us more confidence in manipulating extremely complex template types. At the same time, a lot of enhancements have been made to the language runtime. The emergence of Lambda expressions has made C++ have the "closure" feature of "anonymous functions", which is almost in modern programming languages (such as Python, Swift, etc). It has become commonplace, and the emergence of rvalue references has solved the problem of temporary object efficiency that C++ has long been criticized for.
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 of **modern C++** programming. Although it does not appear as much as C++11, it contains a large number of small and beautiful languages and features (such as structured binding), and the appearance of these features once again corrects our programming paradigm in C++.
Modern C++ also adds a lot of tools and methods to its own standard library, such as `std::thread` at the level of the language itself, which supports concurrent programming and no longer depends on the underlying system on different platforms. The API implements cross-platform support at the language level; `std::regex` provides full regular expression support and more. C++98 has been proven to be a very successful "paradigm", and the emergence of modern C++ further promotes this paradigm, making C++ a better language for system programming and library development. Concepts provide verification on the compile-time of template parameters, further enhancing the usability of the language.
Modern C++ also adds a lot of tools and methods to its standard library, such as `std::thread` at the level of the language itself, which supports concurrent programming and no longer depends on the underlying system on different platforms. The API implements cross-platform support at the language level; `std::regex` provides full regular expression support and more. C++98 has been proven to be a very successful "paradigm", and the emergence of modern C++ further promotes this paradigm, making C++ a better language for system programming and library development. Concepts verify the compile-time of template parameters, further enhancing the usability of the language.
In conclusion, as an advocate and practitioner of C++, we always maintain an open mind to accept new things, and we can promote the development of C++ faster, making this old and novel language more vibrant.
## Targets
- This book assumes that readers are already familiar with traditional C++ (i.e. C++98 or earlier), at least they do not have any difficulty in reading traditional C++ code. In other words, those who have long experience in traditional C++ and people who desire to quickly understand the features of modern C++ in a short period of time are well suited to read the book;
- This book assumes that readers are already familiar with traditional C++ (i.e. C++98 or earlier), at least they do not have any difficulty in reading traditional C++ code. In other words, those who have long experience in traditional C++ and people who desire to quickly understand the features of modern C++ in a short period are well suited to read the book;
- This book introduces to a certain extent of the dark magic of modern C++. However, these magics are very limited, they are not suitable for readers who want to learn advanced C++. The purpose of this book is offering a quick start for modern C++. Of course, advanced readers can also use this book to review and examine themselves on modern C++.
- This book introduces to a certain extent of the dark magic of modern C++. However, these magics are very limited, they are not suitable for readers who want to learn advanced C++. The purpose of this book is to offer a quick start for modern C++. Of course, advanced readers can also use this book to review and examine themselves on modern C++.
## Purpose
The book claims "On the Fly". Its intent is to provide a comprehensive introduction to the relevant features regarding modern C++ (before 2020s).
The book claims "On the Fly". It intends to provide a comprehensive introduction to the relevant features regarding modern C++ (before the 2020s).
Readers can choose interesting content according to the following table of content to learn and quickly familiarize the new features you would like to learn.
Readers should aware that all of these features are not required. It should be learnt when you really need it.
Readers should aware that all of these features are not required. It should be learned when you need it.
At the same time, instead of grammar-only, the book introduces the historical background as simple as possible of its technical requirements, which provides great help in understanding why these features comes out.
At the same time, instead of grammar-only, the book introduces the historical background as simple as possible of its technical requirements, which provides great help in understanding why these features come out.
In addition, 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 after read the book.
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 after reading the book.
## Code
Each chapter of this book has a lot of code. If you encounter problems when writing your own code with the introductory features of the book, you might as well read the source code attached to the book. You can find the book [here](../../code). All the code organized by chapter, the folder name is the chapter number.
Each chapter of this book has a lot of code. If you encounter problems when writing your own code with the introductory features of the book, you might as well read the source code attached to the book. You can find the book [here](../../code). All the code is organized by chapter, the folder name is the chapter number.
## Exercises

View File

@@ -21,9 +21,9 @@ InstalledDir: /Library/Developer/CommandLineTools/usr/bin
## 1.1 Deprecated Features
Before learning modern C++, let's take a look at the main features that have been deprecated since C++11:
Before learning modern C++, let's take a look at the main features that have deprecated since C++11:
> **Note**: Deprecation is not completely unusable, it is only intended to imply that features will disappear from future standards and should be avoided. However, the deprecated features are still part of the standard library, and most of the features are actually "permanently" reserved for compatibility reasons.
> **Note**: Deprecation is not completely unusable, it is only intended to imply that features will disappear from future standards and should be avoided. But, the deprecated features are still part of the standard library, and most of the features are actually "permanently" reserved for compatibility reasons.
- **The string literal constant is no longer allowed to be assigned to a `char *`. If you need to assign and initialize a `char *` with a string literal constant, you should use `const char *` or `auto`.**
@@ -47,11 +47,11 @@ Before learning modern C++, let's take a look at the main features that have bee
- ... and many more
There are also other features such as parameter binding (C++11 provides `std::bind` and `std::function`), `export`, and etc. are also deprecated. These features mentioned above **If you have never used or heard of it, please don't try to understand them. You should move closer to the new standard and learn new features directly**. After all, technology is moving forward.
There are also other features such as parameter binding (C++11 provides `std::bind` and `std::function`), `export` etc. are also deprecated. These features mentioned above **If you have never used or heard of it, please don't try to understand them. You should move closer to the new standard and learn new features directly**. After all, technology is moving forward.
## 1.2 Compatibilities with C
For some force majeure and historical reasons, we had to use some C code (even old C code) in C++, for example, Linux system calls. Before the advent of modern C++, most people talked about "what is the difference between C and C++". Generally speaking, in addition to answering the object-oriented class features and the template features of generic programming, there is no other opinion, or even a direct answer. "Almost" is also a lot of people. The Venn diagram in Figure 1.2 roughly answers the C and C++ related compatibility.
For some force majeure and historical reasons, we had to use some C code (even old C code) in C++, for example, Linux system calls. Before the advent of modern C++, most people talked about "what is the difference between C and C++". Generally speaking, in addition to answering the object-oriented class features and the template features of generic programming, there is no other opinion or even a direct answer. "Almost" is also a lot of people. The Venn diagram in Figure 1.2 roughly answers the C and C++ related compatibility.
![Figure 1.2: Compatabilities between ISO C and ISO C++](../../assets/figures/comparison.png)
@@ -121,7 +121,7 @@ clean:
rm -rf *.o $(TARGET)
```
> Note: Indentation in `Makefile` is a tab instead of a space character. If you copy this code directly into your editor, the tab may be automatically replaced. Please ensure the indentation in the `Makefile`. It is done by tabs.
> **Note**: Indentation in `Makefile` is a tab instead of a space character. If 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.
>
> If you don't know the use of `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.

View File

@@ -92,12 +92,11 @@ We will discuss them in detail later in the [decltype](#decltype) section.
### constexpr
C++ itself already has the concept of constant expressions,
such as 1+2, 3*4. Such expressions always produce the same result
without any side effects. If the compiler can directly optimize
and embed these expressions into the program at compile time,
it will increase the performance of the program.
A very obvious example is in the definition phase of an array:
C++ itself already has the concept of constant expressions, such as 1+2,
3\*4. Such expressions always produce the same result without any side effects.
If the compiler can directly optimize and embed these expressions into the program at
compile-time, it will increase the performance of the program. A very obvious example
is in the definition phase of an array:
```cpp
#include <iostream>
@@ -147,14 +146,14 @@ we need to use the `constexpr` feature introduced in C++11, which will be introd
to solve this problem; for `arr_5`, before 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 own compiler optimizations.
> Note that most compilers now have their compiler optimizations.
> Many illegal behaviors become legal under the compiler's optimization.
> If you need to reproduce the error, you need to use the old version of the compiler.
C++11 provides `constexpr` to let the user explicitly declare that the function or
object constructor will become a constant expression at compile time.
This keyword explicitly tells the compiler that it should verify that `len_foo`
should be a compile time constant expression. Constant expression.
should be a compile-time constant expression. Constant expression.
In addition, the function of `constexpr` can use recursion:
@@ -240,7 +239,7 @@ Initialization is a very important language feature,
the most common one is when the object is initialized.
In traditional C++, different objects have different initialization methods,
such as ordinary arrays, PODs (**P**lain **O**ld **D**ata,
ie classes without constructs, destructors, and virtual functions)
i.e. classes without constructs, destructors, and virtual functions)
Or struct type can be initialized with `{}`,
which is what we call the initialization list.
For the initialization of the class object,
@@ -332,7 +331,7 @@ provided in other languages. In the chapter on containers,
we will learn that C++11 has added a `std::tuple` container for
constructing a tuple that encloses multiple return values. But the flaw
is that C++11/14 does not provide a simple way to get and define
the elements in the tuple directly from the tuple,
the elements in the tuple from 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 of each object is, very troublesome.
@@ -360,7 +359,7 @@ The `auto` type derivation is described in the
## 2.3 Type inference
In traditional C and C++, the types of parameters must be clearly defined, which does not help us to quickly encode, especially when we are faced with a large number of complex template types, we must clearly indicate the type of variables in order to proceed. Subsequent coding, which not only slows down our development efficiency, but also makes the code stinking and long.
In traditional C and C++, the types of parameters must be clearly defined, which does not help us to quickly encode, especially when we are faced with a large number of complex template types, we must indicate the type of variables to proceed. Subsequent coding, which not only slows down our development efficiency 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 of the variable. This makes C++ the same as other modern programming languages, in a way that provides the habit of not having to worry about variable types.
@@ -415,6 +414,7 @@ auto arr = new auto(10); // arr as int *
> **Note**: `auto` cannot be used for function arguments, so the following
> is not possible to compile (considering overloading,
> we should use templates):
>
> ```cpp
> int add(auto x, auto y);
>
@@ -437,7 +437,6 @@ auto arr = new auto(10); // arr as int *
The `decltype` keyword is used to solve the defect that the auto keyword
can only type the variable. Its usage is very similar to `typeof`:
```cpp
decltype(expression)
```
@@ -485,9 +484,9 @@ R add(T x, U y) {
> Note: There is no difference between typename and class in the template parameter list. Before the keyword typename appears, class is used to define the template parameters. However, when defining a variable with [nested dependency type](http://en.cppreference.com/w/cpp/language/dependent_name#The_typename_disambiguator_for_dependent_names) in the template, you need to use typename to eliminate ambiguity.
Such code is actually very ugly, because the programmer must explicitly
Such code is very ugly because the programmer must explicitly
indicate the return type when using this template function.
But in fact we don't know what kind of operation
But in fact, we don't know what kind of operation
the `add()` function will do, and what kind of return type to get.
This problem was solved in C++11. Although you may immediately
@@ -583,7 +582,7 @@ decltype(auto) look_up_a_string_2() {
### if constexpr
As we saw at the beginning of this chapter, we know that C++11 introduces the `constexpr` keyword, which compiles expressions or functions into constant results. A natural idea is that if we introduce this feature into the conditional judgment, let the code complete the branch judgment at compile time, can it make the program more efficient? C++17 introduces the `constexpr` keyword into the `if` statement, allowing you to declare the condition of a constant expression in your code. Consider the following code:
As we saw at the beginning of this chapter, we know that C++11 introduces the `constexpr` keyword, which compiles expressions or functions into constant results. A natural idea is that if we introduce this feature into the conditional judgment, let the code complete the branch judgment at compile-time, can it make the program more efficient? C++17 introduces the `constexpr` keyword into the `if` statement, allowing you to declare the condition of a constant expression in your code. Consider the following code:
```cpp
#include <iostream>
@@ -619,7 +618,7 @@ int main() {
### Range-based for loop
Finally, C++11 introduces a range-based iterative method, and we have the ability to write loops that are as concise
Finally, C++11 introduces a range-based iterative method, and we can write loops that are as concise
as Python, and we can further simplify the previous example:
```cpp
@@ -642,7 +641,7 @@ int main() {
## 2.5 Templates
C++ templates have always been a special art of the language, and templates can even be used independently as a new language. The philosophy of 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, so as to greatly optimize the performance of the runtime. Therefore, templates are also regarded by many as one of the black magic of C++.
C++ templates have always been a special art of the language, and templates can even be used independently as a new language. The philosophy of 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 performance of the runtime. Therefore, templates are also regarded by many as one of the black magic of C++.
### Extern templates
@@ -697,7 +696,7 @@ typedef MagicType<std::vector<T>, std::string> FakeDarkMagic;
C++11 uses `using` to introduce the following form of writing, and at the same time supports the same effect as the traditional `typedef`:
> Usually we use `typedef` to define the alias syntax: `typedef original name new name; `, but the definition syntax for aliases such as function pointers is different, which usually causes a certain degree of difficulty for direct reading.
> Usually, we use `typedef` to define the alias syntax: `typedef original name new name; `, but the definition syntax for aliases such as function pointers is different, which usually causes a certain degree of difficulty for direct reading.
```cpp
typedef int (*process)(void *);
@@ -746,7 +745,7 @@ and there is no need to fix the number of parameters when defining.
template<typename... Ts> class Magic;
```
The template class Magic object can accept unrestricted number of typename as
The template class Magic object can accept an unrestricted number of typename as
a formal parameter of the template, such as the following definition:
```cpp
@@ -799,7 +798,6 @@ the parameter package, but there are two classic processing methods:
Recursion is a very easy way to think of and the most classic approach. This method continually recursively passes template parameters to the function, thereby achieving the purpose of recursively traversing all template parameters:
```cpp
#include <iostream>
template<typename T0>
@@ -833,7 +831,7 @@ void printf2(T0 t0, T... t) {
**3. Initialize list expansion**
Recursive template functions are a standard practice, but the obvious drawback is that you must define a function that terminates recursion.
Recursive template functions are standard practice, but the obvious drawback is that you must define a function that terminates recursion.
Here is a description of the black magic that is expanded using the initialization list:
@@ -880,7 +878,7 @@ auto add(T t, U u) {
The parameters of the template `T` and `U` are specific types.
But there is also a common form of template parameter that allows different literals
to be template parameters, ie non-type template parameters:
to be template parameters, i.e. non-type template parameters:
```cpp
template <typename T, int BufSize>
@@ -896,7 +894,7 @@ buffer_t<int, 100> buf; // 100 as template parameter
```
In this form of template parameters, we can pass `100` as a parameter to the template.
After C++11 introduced the feature of type derivation, we will naturally ask, since the template parameters here
After C++11 introduced the feature of type derivation, we will naturally ask, since the template parameters here.
Passing with a specific literal, can the compiler assist us in type derivation,
By using the placeholder `auto`, there is no longer a need to explicitly specify the type?
Fortunately, C++17 introduces this feature, and we can indeed use the `auto` keyword to let the compiler assist in the completion of specific types of derivation.
@@ -1022,7 +1020,7 @@ struct SubClass3: Base {
### Explicit delete default function
In traditional C++, if the programmer does not provide it, the compiler will default to generating default constructors, copy constructs, assignment operators, and destructors for the object. In addition, C++ also defines operators such as `new` `delete` for all classes. This part of the function can be overridden when the programmer needs it.
In traditional C++, if the programmer does not provide it, the compiler will default to generating default constructors, copy constructs, assignment operators, and destructors for the object. Besides, C++ also defines operators such as `new` `delete` for all classes. This part of the function can be overridden when the programmer needs it.
This raises some requirements: the ability to accurately control the generation of default functions 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 undefined functions will result in compilation or link errors, which is a very unconventional way.
@@ -1085,7 +1083,7 @@ std::cout << new_enum::value3 << std::endl
This section introduces the enhancements to language usability in modern C++, which I believe are the most important features that almost everyone needs to know and use:
1. auto type derivation
1. Auto type derivation
2. Scope for iteration
3. Initialization list
4. Variable parameter template

View File

@@ -10,9 +10,9 @@ order: 3
## 3.1 Lambda Expression
Lambda expressions are one of the most important features in modern C++, and Lambda expressions actually provide a feature like anonymous functions.
Anonymous functions are used when a function is needed, but you dont want to use name to call a function. There are actually many, many scenes like this.
So anonymous functions are almost standard on modern programming languages.
Lambda expressions are one of the most important features in modern C++, and Lambda expressions provide a feature like anonymous functions.
Anonymous functions are used when a function is needed, but you dont want to use a name to call a function. There are many, many scenes like this.
So anonymous functions are almost standard in modern programming languages.
### Basics
@@ -120,7 +120,7 @@ initialize it in the expression.
### Generic Lambda
In the previous section we mentioned that the `auto` keyword cannot be used
In the previous section, we mentioned that the `auto` keyword cannot be used
in the parameter list because it would conflict with the functionality of the template.
But Lambda expressions are not ordinary functions, so Lambda expressions are not templated.
This has caused us some trouble: the parameter table cannot be generalized,
@@ -145,7 +145,7 @@ void lambda_generic() {
Although the features are part of the standard library and not found in runtime,
it enhances the runtime capabilities of the C++ language.
This part of the content is also very important, so put it here for introduction.
This part of the content is also very important, so put it here for the introduction.
### `std::function`
@@ -183,7 +183,6 @@ the call to a function pointer is not type-safe), in other words,
a container of functions. When we have a container for functions,
we can more easily handle functions and function pointers as objects. e.g:
```cpp
#include <functional>
#include <iostream>
@@ -277,7 +276,7 @@ int main() {
```
**xvalue, expiring value** is the concept proposed by C++11 to introduce
rvalue references (so in traditional C++, pure rvalue and rvalue are the same concept),
rvalue references (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,
@@ -299,11 +298,11 @@ And then destroy `temp`, if this `temp` is very large, this will cause a lot of
overhead (this is the problem that traditional C++ has been criticized for).
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 modified.
The xvalue defines an behavior in which temporary values can be identified
while being able to be moved.
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 modified. The xvalue defines behavior in which temporary values can be
identified while being able to be moved.
After C++11, the compiler did some work for us, where the lvalue `temp`
is subjected to this implicit rvalue conversion,
@@ -396,9 +395,9 @@ The reason is simple because Fortran needs it.
Traditional C++ has designed the concept of copy/copy for class objects
through copy constructors and assignment operators,
but in order to implement the movement of resources,
but to implement the movement of resources,
The caller must use the method of copying and then destructing first,
otherwise you need to implement the interface of the mobile object yourself.
otherwise, you need to implement the interface of the mobile object yourself.
Imagine moving your home directly to your new home instead of
copying everything (rebuy) to your new home.
Throwing away (destroying) all the original things is a very anti-human thing.
@@ -519,7 +518,7 @@ resulting in a reference collapse rule that allows us to reference references,
both lvalue and rvalue. But follow the rules below:
| Function parameter type | Argument parameter type | Post-derivation function parameter type |
| :--------: | :--------: | :-------------: |
| :---------------------: | :---------------------: | :-------------------------------------: |
| T& | lvalue ref | T& |
| T& | rvalue ref | T& |
| T&& | lvalue ref | T& |
@@ -527,7 +526,7 @@ both lvalue and rvalue. But follow the rules below:
Therefore, the use of `T&&` in a template function may not be able to make an rvalue reference, and when a lvalue is passed, a reference to this function will be derived as an lvalue.
More precisely, ** no matter what type of reference the template parameter is, the template parameter can be derived as a right reference type** if and only if the argument type is a right reference.
This makes `v` a successful delivery of lvalues.
This makes `v` successful delivery of lvalues.
Perfect forwarding is based on the above rules. The so-called perfect forwarding is to let us pass the parameters,
Keep the original parameter type (lvalue reference keeps lvalue reference, rvalue reference keeps rvalue reference).
@@ -583,11 +582,11 @@ static_cast<T&&> param passing: lvalue reference
Regardless of whether the pass parameter is an lvalue or an rvalue, the normal pass argument will forward the argument as an lvalue.
So `std::move` will always accept an lvalue, which forwards the call to `reference(int&&)` to output the rvalue reference.
Only `std::forward` does not cause any extra copies, and ** perfectly forwards ** (passes) the arguments of the function to other functions that are called internally.
Only `std::forward` does not cause any extra copies and ** perfectly forwards ** (passes) the arguments of the function to other functions 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 of the parameters. From the point of view of the phenomenon,
`std::forward<T>(v)` is exactly the same as `static_cast<T&&>(v)`.
`std::forward<T>(v)` is the same as `static_cast<T&&>(v)`.
Readers may be curious as to why a statement can return values for two types of returns.
Let's take a quick look at the concrete implementation of `std::forward`. `std::forward` contains two overloads:
@@ -614,7 +613,7 @@ When `std::forward` accepts an lvalue, `_Tp` is deduced to the lvalue, so the re
`_Tp` is derived as an rvalue reference, and based on the collapse rule, the return value becomes the rvalue of `&& + &&`.
It can be seen that the principle of `std::forward` is to make clever use of the differences in template type derivation.
At this point we can answer the question: Why is `auto&&` the safest way to use looping statements?
At this point, we can answer the question: Why is `auto&&` the safest way to use looping statements?
Because when `auto` is pushed to a different lvalue and rvalue reference, the collapsed combination with `&&` is perfectly forwarded.
## Conclusion
@@ -622,6 +621,7 @@ Because when `auto` is pushed to a different lvalue and rvalue reference, the co
This chapter introduces the most important runtime enhancements in modern C++, and I believe that all the features mentioned in this section are worth knowing:
Lambda expression
1. Function object container std::function
2. rvalue reference

View File

@@ -12,13 +12,13 @@ order: 4
### `std::array`
When you see this container, you will definitely have this problem:
When you see this container, you will have this problem:
1. Why introduce `std::array` instead of `std::vector` directly?
2. Already have a traditional array, why use `std::array`?
First answer the first question. Unlike `std::vector`, the size of the `std::array` object is fixed. If the container size is fixed, then the `std::array` container can be used first.
In addition, since `std::vector` is automatically expanded, when a large amount of data is stored, and the container is deleted,
First, answer the first question. Unlike `std::vector`, the size of the `std::array` object is fixed. If the container size is fixed, then the `std::array` container can be used first.
Also, since `std::vector` is automatically expanded, when a large amount of data is stored, and the container is deleted,
The container does not automatically return the corresponding memory of the deleted element. In this case, you need to manually run `shrink_to_fit()` to release this part of the memory.
```cpp
@@ -103,7 +103,7 @@ std::sort(arr.begin(), arr.end());
### `std::forward_list`
`std::forward_list` is a list container, and the usage is basically similar to `std::list`, so we don't spend a lot of time introducing it.
`std::forward_list` is a list container, and the usage is similar to `std::list`, so we don't spend a lot of time introducing it.
Need to know is that, unlike the implementation of the doubly linked list of `std::list`, `std::forward_list` is implemented using a singly linked list.
Provides element insertion of `O(1)` complexity, does not support fast random access (this is also a feature of linked lists),
@@ -115,7 +115,7 @@ We are already familiar with the ordered container `std::map`/`std::set` in trad
The average complexity of 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 of the `<` operator.
The elements in the unordered container are not sorted, and the internals are implemented by the Hash table. The average complexity of inserting and searching for elements is `O(constant)`,
The elements in the unordered container are not sorted, and the internals is implemented by the Hash table. The average complexity of inserting and searching for elements is `O(constant)`,
Significant performance gains can be achieved without concern for the order of the elements inside the container.
C++11 introduces two sets of unordered containers: `std::unordered_map`/`std::unordered_multimap` and
@@ -278,7 +278,7 @@ auto new_tuple = std::tuple_cat(get_student(1), std::move(t));
```
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 of a tuple, which can:
First, we need to know the length of a tuple, which can:
```cpp
template <typename T>
@@ -297,7 +297,7 @@ for(int i = 0; i != tuple_len(new_tuple); ++i)
## Conclusion
This chapter briefly introduces the new containers in modern C++. Their usage is similar to that of the existing containers in C++. It is relatively simple, and you can choose the containers you need to use according to the actual scene, so as to get better performance.
This chapter briefly introduces the new containers in modern C++. Their usage is similar to that of 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 performance.
Although `std::tuple` is effective, the standard library provides limited functionality and there is no way to meet the requirements of runtime indexing and iteration. Fortunately, we have other methods that we can implement on our own.

View File

@@ -18,11 +18,11 @@ In traditional C++, "remembering" to manually release resources is not always a
So the usual practice is that for an object, we apply for space when constructor, and free space when the destructor (called when leaving the scope).
That is, we often say that the RAII resource acquisition is the initialization technology.
There are exceptions to everything, we always have the need to allocate objects on free storage. In traditional C++ we have to use `new` and `delete` to "remember" to release resources. C++11 introduces the concept of smart pointers, using the idea of reference counting, so that programmers no longer need to care about manually releasing memory.
There are exceptions to everything, we always need to allocate objects on free storage. In traditional C++ we have to use `new` and `delete` to "remember" to release resources. C++11 introduces the concept of smart pointers, using the idea of reference counting so that programmers 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 file `<memory>`.
> Note: The reference count is not garbage collection. The reference 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 clearly indicate the life cycle of resources.
> More clearly and indicate the life cycle of resources.
## 5.2 `std::shared_ptr`
@@ -177,7 +177,7 @@ int main() {
}
```
The result is that A and B will not be destroyed. This is because the pointer inside a, b also references `a, b`, which makes the reference count of `a, b` become 2, leaving the scope. When the `a, b` smart pointer is destructed, it can only cause the reference count of this area to be decremented by one. This causes the memory area reference count pointed to by the `a, b` object to be non-zero, but the external has no way to find this area, it also caused a memory leak, as shown in Figure 5.1:
The result is that A and B will not be destroyed. This is because the pointer inside a, b also references `a, b`, which makes the reference count of `a, b` becomes 2, leaving the scope. When the `a, b` smart pointer is destructed, it can only cause the reference count of this area to be decremented by one. This causes the memory area reference count pointed to by the `a, b` object to be non-zero, but the external has no way to find this area, it also caused a memory leak, as shown in Figure 5.1:
![Figure 5.1](../../assets/figures/pointers1_en.png)
@@ -187,7 +187,7 @@ The solution to this problem is to use the weak reference pointer `std::weak_ptr
In the above figure, only B is left in the last step, and B does not have any smart pointers to reference it, so this memory resource will also be released.
`std::weak_ptr` has no `*` operator and `->` operator, so it can't operate on resources. Its only function is to check if `std::shared_ptr` exists, its `expired()` method can return `false` when the resource is not released, otherwise it returns `true`.
`std::weak_ptr` has no `*` operator and `->` operator, so it can't operate on resources. Its only function is to check if `std::shared_ptr` exists, its `expired()` method can return `false` when the resource is not released, otherwise, it returns `true`.
## Conclusion

View File

@@ -27,17 +27,15 @@ Regular expressions act as a template to match a character pattern to the string
### Ordinary characters
Normal characters include all printable and unprintable characters that
are not explicitly specified as metacharacters. This includes all uppercase
Normal characters include all printable and unprintable characters that are not explicitly specified 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 of a regular expression. See the table below:
A special character is a character with special meaning in a regular expression and is also the core matching syntax of a regular expression. See the table below:
| Special characters | Description |
|:---:|:------------------------------------------------------|
| :----------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `$` | Matches the end position of the input string. |
| `(`,`)` | Marks the start and end of a subexpression. Subexpressions can be obtained for later use. |
| `*` | Matches the previous subexpression zero or more times. |
@@ -55,7 +53,7 @@ and is also the core matching syntax of a regular expression. See the table belo
The qualifier is used to specify how many times a given component of a regular expression must appear to satisfy the match. See the table below:
| Character | Description |
|:---:|:------------------------------------------------------|
| :-------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `*` | matches the previous subexpression zero or more times. For example, `foo*` matches `fo` and `foooo`. `*` is equivalent to `{0,}`. |
| `+` | matches the previous subexpression one or more times. For example, `foo+` matches `foo` and `foooo` but does not match `fo`. `+` is equivalent to `{1,}`. |
| `?` | matches the previous subexpression zero or one time. For example, `Your(s)?` can match `Your` in `Your` or `Yours`. `?` is equivalent to `{0,1}`. |
@@ -67,7 +65,7 @@ With these two tables, we can usually read almost all regular expressions.
## 6.2 `std::regex` and Its Related
The most common way to match string content is to use regular expressions. Unfortunately, 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-performance language. In the development of background services, the use of regular expressions is also used when judging URL resource links. The most mature and common practice in industry.
The most common way to match string content is to use regular expressions. Unfortunately, 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-performance language. In the development of background services, the use of 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 of `boost`. C++11 officially incorporates the processing of regular expressions into the standard library, providing standard support from the language level and no longer relying on third parties.
@@ -77,7 +75,7 @@ We use a simple example to briefly introduce the use of this library. Consider t
- `[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 of 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 of this regular expression to match is a text file consisting of pure lowercase letters.
`std::regex_match` is used to match strings and regular expressions, and there are many different overloaded forms. The simplest form is to pass `std::string` and a `std::regex` to match. When the match is successful, it will return `true`, otherwise it will return `false`. For example:
`std::regex_match` is used to match strings and regular expressions, and there are many different overloaded forms. The simplest form is to pass `std::string` and a `std::regex` to match. When the match is successful, it will return `true`, otherwise, it will return `false`. For example:
```cpp
#include <iostream>

View File

@@ -28,7 +28,7 @@ int main() {
## 7.2 Mutex and Critical Section
We have already learned the basics of concurrency technology in the operating system, or in the database, and `mutex` is one of the cores.
We have already learned the basics of concurrency technology in the operating system, or the database, and `mutex` is one of the cores.
C++11 introduces a class related to `mutex`, with all related functions in the `<mutex>` header file.
`std::mutex` is the most basic `mutex` class in C++11, and you can create a mutex by instantiating `std::mutex`.
@@ -114,10 +114,10 @@ int main() {
## 7.3 Future
The Future is represented by `std::future`, which provides a way to access the results of asynchronous operations. This sentence is very difficult to understand.
In order to understand this feature, we need to understand the multi-threaded behavior before C++11.
To understand this feature, we need to understand the multi-threaded behavior before C++11.
Imagine if our main thread A wants to open a new thread B to perform some of 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 of B.
At this time, thread A may be busy with other things and have no time to take into account the results of B.
So we naturally hope to get the result of thread B at a certain time.
Before the introduction of `std::future` in C++11, the usual practice is:
@@ -217,15 +217,15 @@ int main() {
}
```
It is worth mentioning that although we can use `notify_one()` in the producer, it is not really recommended to use it here.
Because in the case of multiple consumers, our consumer implementation simply gives up the lock holding, which makes it possible for other consumers to compete for this lock, so as to better utilize the concurrency between multiple consumers. Having said that, but in fact because of the exclusivity of `std::mutex`,
We simply can't expect multiple consumers to be able to actually produce content in a parallel consumer queue, and we still need a more granular approach.
It is worth mentioning that although we can use `notify_one()` in the producer, it is not recommended to use it here.
Because in the case of multiple consumers, our consumer implementation simply gives up the lock holding, which makes it possible for other consumers to compete for this lock, to better utilize the concurrency between multiple consumers. Having said that, but in fact because of the exclusivity of `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.
## 7.5 Atomic Operation and Memory Model
Careful readers may be tempted by the fact that the example of the producer consumer model in the previous section may have compiler optimizations that cause program errors.
Careful readers may be tempted by the fact that the example of the producer-consumer model in the previous section may have compiler optimizations that cause program errors.
For example, the boolean `notified` is not modified by `volatile`, and the compiler may have optimizations for this variable, such as the value of a register.
As a result, the consumer thread can never observe the change of this value. This is a good question. To explain this problem, we need to further discuss the concept of memory model introduced from C++11. Let's first look at a question. What is the output of the following code?
As a result, the consumer thread can never observe the change of this value. This is a good question. To explain this problem, we need to further discuss the concept of the memory model introduced from C++11. Let's first look at a question. What is the output of the following code?
```cpp
#include <thread>
@@ -254,19 +254,19 @@ int main() {
```
Intuitively, `a = 5;` seems in `t2` seems to always execute before `flag = 1;`, and `while (flag != 1)` in `t1` seems to guarantee `std ::cout << "b = " << b << std::endl;` will not be executed before the mark is changed. Logically, it seems that the value of `b` should be equal to 5.
But the actual situation is much more complicated than this, or the code itself is undefined behavior, because for `a` and `flag`, they are read and written in two parallel threads.
There has been competition. In addition, even if we ignore competing reading and writing, it is still possible to receive out-of-order execution of the CPU, and the impact of the compiler on the rearrangement of instructions.
But the actual situation is much more complicated than this, or the code itself is undefined behavior because, for `a` and `flag`, they are read and written in two parallel threads.
There has been competition. Also, even if we ignore competing for reading and writing, it is still possible to receive out-of-order execution of the CPU and the impact of the compiler on the rearrangement of instructions.
Cause `a = 5` to occur after `flag = 1`. Thus `b` may output 0.
### Atomic Operation
`std::mutex` can solve the problem of concurrent read and write, but the mutex is an operating system level function.
`std::mutex` can solve the problem of concurrent read and write, but the mutex is an operating system-level function.
This is because the implementation of a mutex usually contains two basic principles:
1. Provide automatic state transition between threads, that is, "lock" state
2. Ensure that the memory of the manipulated variable is isolated from the critical section during the mutex operation
This is a very strong set of synchronization conditions, in other words, when it is finally compiled into a CPU instruction, it will behave as a lot of instructions (we will look at how to implement a simple mutex later).
This is a very strong set of synchronization conditions, in other words when it is finally compiled into a CPU instruction, it will behave like a lot of instructions (we will look at how to implement a simple mutex later).
This seems too harsh for 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.
@@ -397,9 +397,9 @@ Weakening the synchronization conditions between processes, usually we will cons
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 information to determine that `x` is not related to `y`)
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 information 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 future, but does not require the observed time. So we can even strengthen this condition a bit, for example, to specify that the time observed for an operation is always bounded. Of course this is no longer within our discussion.
4. Final Consistency: It is the weakest consistency requirement. It only guarantees that an operation will be observed at a certain point in the future, but does not require the observed time. So we can even strengthen this condition a bit, for example, to specify that the time observed for an operation is always bounded. Of course, this is no longer within our discussion.
```
x.store(3) x.store(4)
@@ -421,7 +421,7 @@ Weakening the synchronization conditions between processes, usually we will cons
### Memory Orders
In order to achieve the ultimate performance and achieve consistency of various strength requirements, C++11 defines six different memory sequences for atomic operations. The option `std::memory_order` expresses four synchronization models between multiple threads:
To achieve the ultimate performance and achieve consistency of various strength requirements, C++11 defines six different memory sequences for atomic operations. The option `std::memory_order` expresses four synchronization models between multiple threads:
1. Relaxed model: Under this model, atomic operations within a single thread are executed sequentially, and instruction reordering is not allowed, but the order of atomic operations between different threads is arbitrary. The type is specified by `std::memory_order_relaxed`. Let's look at an example:
@@ -440,7 +440,6 @@ In order to achieve the ultimate performance and achieve consistency of various
std::cout << "current counter:" << counter << std::endl;
```
2. Release/consumption model: In this model, we begin to limit the order of operations between processes. If a thread needs to modify a value, but another thread will have a dependency on that operation of the value, that is, the latter depends. former. Specifically, thread A has completed three writes to `x`, and thread `B` relies only on the third `x` write operation, regardless of the first two write behaviors of `x`, then `A ` When active `x.release()` (ie using `std::memory_order_release`), the option `std::memory_order_consume` ensures that `B` observes `A` when calling `x.load()` Three writes to `x`. Let's look at an example:
```cpp
@@ -463,9 +462,9 @@ In order to achieve the ultimate performance and achieve consistency of various
consumer.join();
```
3. Release/Acquire model: Under this model, we can further tighten the order of atomic operations between different threads, specifying the timing between releasing `std::memory_order_release` and getting `std::memory_order_acquire`. **All** write operations before the release operation are visible to any other thread, ie, happens-before.
3. Release/Acquire model: Under this model, we can further tighten the order of atomic operations between different threads, specifying the timing between releasing `std::memory_order_release` and getting `std::memory_order_acquire`. **All** write operations before the release operation is visible to any other thread, i.e., happens before.
As you can see, `std::memory_order_release` ensures that the write behavior after it does not occur before the release operation, which is a forward barrier, and`std::memory_order_acquire` ensures that its previous write behavior does not occur after this acquisition operation, there is a backward barrier. For the `std::memory_order_acq_rel` option, it combines the characteristics of the two and uniquely determines a memory barrier, so that the current thread's reading and writing of memory will not be rearranged before and after this operation.
As you can see, `std::memory_order_release` ensures that the write behavior after it does not occur before the release operation, which is a forward barrier, and`std::memory_order_acquire` ensures that its previous write behavior does not occur after this acquisition operation, there is a backward barrier. For the `std::memory_order_acq_rel` option, combines the characteristics of the two and uniquely determines a memory barrier, so that the current thread's reading and writing of memory will not be rearranged before and after this operation.
Let's check an example:
@@ -493,9 +492,9 @@ In order to achieve the ultimate performance and achieve consistency of various
acquire.join();
```
In this case we used `compare_exchange_strong`, which is the Compare-and-swap primitive, which has a weaker version, `compare_exchange_weak`, which allows a failure to be returned even if the exchange is successful. The reason is due to a false failure on some platforms, specifically, when the CPU performs a context switch, another thread loads the same address to produce an inconsistency. In addition, the performance of `compare_exchange_strong` may be slightly worse than `compare_exchange_weak`, but in most cases, `compare_exchange_strong` should be limited.
In this case we used `compare_exchange_strong`, which is the Compare-and-swap primitive, which has a weaker version, `compare_exchange_weak`, which allows a failure to be returned even if the exchange is successful. The reason is due to a false failure on some platforms, specifically when the CPU performs a context switch, another thread loads the same address to produce an inconsistency. In addition, the performance of `compare_exchange_strong` may be slightly worse than `compare_exchange_weak`, but in most cases, `compare_exchange_strong` should be limited.
4. Sequential Consistent Model: Under this model, atomic operations satisfy sequence consistency, which in turn can cause performance loss. It can be specified explicitly by `std::memory_order_seq_cst`. Let's look a final example:
4. Sequential Consistent Model: Under this model, atomic operations satisfy sequence consistency, which in turn can cause performance loss. It can be specified explicitly by `std::memory_order_seq_cst`. Let's look at a final example:
```cpp
std::atomic<int> counter = {0};
@@ -514,12 +513,11 @@ In order to achieve the ultimate performance and achieve consistency of various
This example is essentially the same as the first loose model example. Just change the memory order of the atomic operation to `memory_order_seq_cst`. Interested readers can write their own programs to measure the performance difference caused by these two different memory sequences.
## Conclusion
The C++11 language layer provides support for concurrent programming. This section briefly introduces `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 of the most important features of C++11.
They provide an critical foundation for standardized high performance computing for C++.
They provide a critical foundation for standardized high-performance computing for C++.
## Exercises

View File

@@ -13,7 +13,7 @@ order: 9
### `long long int`
`long long int` is not the first to be introduced in C++11.
In fact, as early as C99, `long long int` has been included in the C standard,
As early as C99, `long long int` has been included in the C standard,
so most compilers already support it.
C++11 now formally incorporate it into the standard library,
specifying a `long long int` type with at least 64 bits.
@@ -22,12 +22,10 @@ specifying a `long long int` type with at least 64 bits.
One of the big advantages of C++ over C is that
C++ itself defines a complete set of exception handling mechanisms.
However, before C++11, almost no one used to write
an exception declaration expression after the function name.
However, before C++11, almost no one used to write an exception declaration expression after the function name.
Starting from 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.
How to work and how to use it, you should not take the initiative to understand it.
C++11 simplifies exception declarations into two cases:
@@ -47,7 +45,7 @@ immediately 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`.
otherwise, it returns `false`.
```cpp
#include <iostream>
@@ -190,7 +188,7 @@ int main() {
}
```
where `std::max_align_t` requires exactly the same alignment for each scalar type, so it has almost no difference in maximum scalars.
where `std::max_align_t` requires the same alignment for each scalar type, so it has almost no difference in maximum scalars.
In turn, the result on most platforms is `long double`, so the alignment requirement for `AlignasStorage` we get here is 8 or 16.
## Conclusion

View File

@@ -13,14 +13,14 @@ For example, as early as C++11, the `Concept`,
which was eager to call for high-altitude but ultimately lost, is now on the line.
The C++ Organizing Committee decided to vote to finalize C++20 with many proposals,
such as **Concepts**/**Module**/**Coroutine**/**Ranges**/ and so on.
In this chapter we'll take a look at some of the important features that
In this chapter, we'll take a look at some of the important features that
C++20 will introduce.
## Concept
Concept is a further enhancement to C++ template programming.
The concept is a further enhancement to C++ template programming.
In simple terms, the concept is a compile-time feature.
It allows the compiler to evaluate template parameters at compile time,
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 often encounter a variety of heinous errors.
This is because we have so far been unable to check and limit template parameters.