diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml
index a9d7187d..b7608bb6 100644
--- a/.github/workflows/website.yml
+++ b/.github/workflows/website.yml
@@ -9,21 +9,38 @@ jobs:
build:
name: Website
runs-on: ubuntu-latest
- timeout-minutes: 60
+ timeout-minutes: 20
steps:
- - uses: actions/checkout@v2
- - name: build
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ cache: npm
+ cache-dependency-path: website/package-lock.json
+
+ # The Hexo site only needs Node and Python 3 (preinstalled on the
+ # runner). The old pipeline shelled out to a hand-maintained 3.4 GB
+ # texlive Docker image just to also build the PDF/epub; that image
+ # silently drifted to a 2019 Node and broke the build. We deploy the
+ # website only here. The PDF/epub already on the server are left in
+ # place (scp overwrites, never deletes), so their download links keep
+ # working until those artifacts are rebuilt separately.
+ - name: Install website dependencies
+ run: npm ci --prefix website
+
+ - name: Build website
+ run: cd website && make
+
+ - name: Deploy to server
env:
USER: ${{ secrets.SERVER_USER }}
TARGET: ${{ secrets.SERVER_PATH }}
KEY: ${{ secrets.SERVER_KEY }}
DOMAIN: ${{ secrets.SERVER_DOMAIN }}
run: |
- make build
- mkdir ~/.ssh
+ mkdir -p ~/.ssh
echo "$KEY" | tr -d '\r'> ~/.ssh/id_ed25519
chmod 400 ~/.ssh/id_ed25519
- eval "$(ssh-agent -s)"
- ssh-add ~/.ssh/id_ed25519
- ssh-keyscan -H $DOMAIN>> ~/.ssh/known_hosts
- scp -r website/public/modern-cpp/* $USER@$DOMAIN:$TARGET
\ No newline at end of file
+ ssh-keyscan -H "$DOMAIN">> ~/.ssh/known_hosts
+ scp -i ~/.ssh/id_ed25519 -r website/public/modern-cpp/* "$USER@$DOMAIN:$TARGET"
diff --git a/.gitignore b/.gitignore
index 1c18bcf2..9f221977 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,10 @@ website/src/modern-cpp/zh-cn/*
website/src/modern-cpp/en-us/*
website/src/modern-cpp/exercises
website/src/modern-cpp/code
+website/src/modern-cpp/assets/alipay.jpg
website/src/modern-cpp/assets/cover-2nd-en.png
website/src/modern-cpp/assets/cover-2nd.png
-website/src/modern-cpp/assets/figures/*
\ No newline at end of file
+website/src/modern-cpp/assets/cover-2nd-en-logo.png
+website/src/modern-cpp/assets/cover-2nd-logo.png
+website/src/modern-cpp/assets/figures/*
+website/src/modern-cpp/assets/wechat.jpg
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 054bc2ad..768f20a7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,7 +2,7 @@
## Submit Issue
-C++ 11/14/17 issue is used to track the principle description error, `typo` error, and the questions to the author of the book.
+The issue tracker for this book (covering C++11 to C++26) is used to track principle description errors, `typo` errors, and questions to the author of the book.
- Usually, you may encounter typos, semantic errors, grammatical errors, and etc. These are all `typo` errors. If an error has caused some obstacles to your reading and you strongly believe that the `typo` will also affect others reading, then you are very welcome to [submit issue](https://github.com/changkun/modern-cpp-tutorial/issues) to report the `typo` error.
@@ -15,15 +15,15 @@ Report the error immediately by [submitting issue](https://github.com/changkun/m
## Pull Request
-"C++ 11/14/17 On the Fly" is open source so that everyone can contribute to contribute via a PR. However, it is required to read the following instructions carefully before submitting your pull request:
+"Modern C++ Tutorial: C++11 to C++26 On the Fly" is open source so that everyone can contribute via a PR. Please read the following instructions carefully before submitting your pull request:
- Before you submit your pull request, make sure that the [issue list](https://github.com/changkun/modern-cpp-tutorial/issues) already contains the problem you want to solve. If not, please refer to the **Submit Issue** section.
-- Make sure your PR has improved more than 50 `typo` errors, otherwise please do not submit a PR.
+- Fixes of any size are welcome. For trivial `typo` fixes, please consider batching several of them into a single PR rather than opening many tiny PRs.
- For a PR that fixes principled errors, please don't hesitate, all of the readers of the book are very grateful for your contribution!
-- If you would like to be a co-author of this book, please send an email to ask: `hi at changkun dot us`.
+- If you would like to be a co-author of this book, please send an email to ask: `hi at changkun dot de`.
Since this repository provides a variety of reading approaches, thus make sure you have checked all items in the following checklist:
@@ -35,7 +35,7 @@ Since this repository provides a variety of reading approaches, thus make sure y
## 提交 Issue
-『C++ 11/14/17/20』的 issue 用于追踪书中存在的原则性的描述错误、存在的 `typo` 错误,以及向本书作者提问等。
+本书(覆盖 C++11 到 C++26)的 issue 用于追踪书中存在的原则性的描述错误、存在的 `typo` 错误,以及向本书作者提问等。
- 通常情况下,你可能会发现书中某个段落存在错别字、语义错误、文法错误等。
这都是 `typo` 错误。如果该错误已经对你的阅读造成了一定障碍,
@@ -51,18 +51,18 @@ Since this repository provides a variety of reading approaches, thus make sure y
- 如果你在阅读本书的时候发现有部分内容难于理解,也欢迎[提交 issue](https://github.com/changkun/modern-cpp-tutorial/issues) 来询问作者表达你的疑惑。
作者会根据实际情况重新优化这一部分的内容,进而帮助他人更易阅读这部分的内容。
-- 我们也欢迎你提交针对本书内容的相关建议,具体来说如果你认为书中未涉及的某个模块或者文件的源码值得讨论,也欢迎 [提交 issue](https://github.com/changkun/go-under-the-hood/issues) 来进一步讨论。
+- 我们也欢迎你提交针对本书内容的相关建议,具体来说如果你认为书中未涉及的某个模块或者文件的源码值得讨论,也欢迎 [提交 issue](https://github.com/changkun/modern-cpp-tutorial/issues) 来进一步讨论。
## 提交 Pull request
-『C++ 11/14/17/20』是一本开源书籍,任何人都可以参与贡献自己 PR。但在提交 PR 之前请仔细阅读下面的说明:
+『现代 C++ 教程:高速上手 C++11 到 C++26』是一本开源书籍,任何人都可以参与贡献自己 PR。但在提交 PR 之前请仔细阅读下面的说明:
- 当你认为需要提交一个 PR 时,请确保 [issue 列表](https://github.com/changkun/modern-cpp-tutorial/issues)中,已经包含了你想要解决的问题。
如果没有,请参考**提交 Issue** 一节中的描述,提交你的 issue,再提交你的 PR。
-- 当你准备提交一个 typo 错误的 PR 时,请确保你的 PR 改进了 **超过 50 个汉字(或英文单词)** 的 `typo` 错误,否则请不要提交 PR。
+- 任何大小的修正都欢迎。对于细小的 `typo` 修正,建议将多处一并合入到同一个 PR 中,而不是分散为许多零碎的 PR。
- 对于一个修复原则性错误的 PR,请不要犹豫,笔者对此表示非常感谢!
-- 如果非常喜欢本书,以至于希望参与本书的合著,成为作者,请发邮件询问:`hi at changkun dot us`。
+- 如果非常喜欢本书,以至于希望参与本书的合著,成为作者,请发邮件询问:`hi at changkun dot de`。
本仓库提供了多种阅读方式,如果你提交一个 Pull request,则请确保你检查的如下的 checklist:
diff --git a/README-zh-cn.md b/README-zh-cn.md
index 1b0aef83..70974d5d 100644
--- a/README-zh-cn.md
+++ b/README-zh-cn.md
@@ -1,12 +1,12 @@
logo
-# 现代 C++ 教程:高速上手 C++11/14/17/20
+# 现代 C++ 教程:高速上手 C++11 到 C++26
- [](./README.md) [](./README-zh-cn.md) [](./assets/donate.md)
+[](https://github.com/changkun/modern-cpp-tutorial/actions/workflows/website.yml) [](./README.md) [](./README-zh-cn.md) [](./assets/donate.md)
## 本书目的
-本书号称『高速上手』,从内容上对二十一世纪二十年代之前产生 C++ 的相关特性做了非常相对全面的介绍,读者可以自行根据下面的目录选取感兴趣的内容进行学习,快速熟悉需要了解的内容。这些特性并不需要全部掌握,只需针对自己的使用需求和特定的应用场景,学习、查阅最适合自己的新特性即可。
+本书号称『高速上手』,从内容上对现代 C++(从 C++11 到 C++26)的相关特性做了相对全面的介绍,读者可以自行根据下面的目录选取感兴趣的内容进行学习,快速熟悉需要了解的内容。这些特性并不需要全部掌握,只需针对自己的使用需求和特定的应用场景,学习、查阅最适合自己的新特性即可。
同时,本书在介绍这些特性的过程中,尽可能简单明了的介绍了这些特性产生的历史背景和技术需求,这为理解这些特性、运用这些特性提供了很大的帮助。
@@ -50,15 +50,8 @@ $ make build
笔者时间和水平有限,如果读者发现书中内容的错误,欢迎提 [Issue](https://github.com/changkun/modern-cpp-tutorial/issues),或者直接提 [Pull request](https://github.com/changkun/modern-cpp-tutorial/pulls)。详细贡献指南请参考[如何参与贡献](CONTRIBUTING.md),由衷感谢每一位指出本书中出现错误的读者,包括但不限于 [Contributors](https://github.com/changkun/modern-cpp-tutorial/graphs/contributors)。
-
本项目还由以下产品提供赞助支持:
-
-
-
-
-
-
## 许可
-知识共享许可协议
+知识共享许可协议
-本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](./LICENSE)。
+本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](./LICENSE)。
diff --git a/README.md b/README.md
index 537b6adb..094044ac 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,11 @@
logo
-# Modern C++ Tutorial: C++11/14/17/20 On the Fly
+# Modern C++ Tutorial: C++11 to C++26 On the Fly
- [](./README.md) [](./README-zh-cn.md) [](./assets/donate.md)
+[](https://github.com/changkun/modern-cpp-tutorial/actions/workflows/website.yml) [](./README.md) [](./README-zh-cn.md) [](./assets/donate.md)
## Purpose
-The book claims to be "On the Fly". Its intent is to provide a comprehensive introduction to the relevant features regarding modern C++ (before 2020s).
+The book claims to be "On the Fly". Its intent is to provide a comprehensive introduction to the relevant features of modern C++ (from C++11 through C++26).
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 be aware that not all of these features are required. Instead, it should be learned when you really need it.
@@ -56,13 +56,6 @@ The author has limited time and language skills. If readers find any mistakes in
The author is grateful to all contributors, including but not limited to [Contributors](https://github.com/changkun/modern-cpp-tutorial/graphs/contributors).
-This project is also supported by:
-
-
-
-
-
-
## Licenses
-Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](./LICENSE).
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](./LICENSE).
diff --git a/book/en-us/00-preface.md b/book/en-us/00-preface.md
index 83835094..5fe564bb 100644
--- a/book/en-us/00-preface.md
+++ b/book/en-us/00-preface.md
@@ -10,14 +10,14 @@ order: 0
## Introduction
-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.
+The C++ programming language owns a fairly large user group. From the advent of C++98 to the official finalization of C++11, it has continued to stay relevant. 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 integrated into the C++ language and infuse it with new vitality.
+C++ programmers who are still using **traditional C++** (this book refers to C++98 and its previous 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 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.
+**Modern C++** (this book refers to C++11 through C++26) introduces many features into traditional C++ which bring the entire language to a new level of modernization. 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 given C++ the "closure" feature of "anonymous functions", which are in almost all 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 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.
+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.
@@ -27,10 +27,22 @@ In conclusion, as an advocate and practitioner of C++, we always maintain an ope
- 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++.
+## How to read this book
+
+Modern C++ is large, and you do not need to read every page in order. This book assumes you are already comfortable with the basics of C++ — roughly the C++11-era language: classes, templates, and the common standard-library containers. If that describes you (for example, you learned some C++ at university), feel free to skim the parts you already know and focus on what is new to you.
+
+Several aids are built in to help you navigate:
+
+- Each feature is marked with the standard that introduced it (for example, *(since C++17)*), so you can tell at a glance what is newer than the C++ you already know.
+- [Appendix 3](./appendix3.md) is a feature index that maps each feature to the chapter where it appears and the standard it came from — handy for jumping straight to "what's new in C++20", or for looking a feature up later.
+- The book is organized in three parts: the **language core** (Chapters 1–3), the **standard library** (Chapters 4–9), and a **tour by standard version** (Chapters 10–12, covering C++20, C++23, and the forthcoming C++26).
+
+Rather than trying to memorize every feature, treat this book as a reference you return to: reach for a feature when a real problem calls for it.
+
## Purpose
-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.
+The book claims "On the Fly". It intends to provide a comprehensive introduction to the relevant features of modern C++, from C++11 all the way to C++26.
+Readers can choose interesting content according to the following table of contents to learn and quickly familiarize themselves with the new features that are available.
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 come out.
@@ -39,14 +51,14 @@ Also, the author would like to encourage that readers should be able to use mode
## 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 is 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](https://github.com/changkun/modern-cpp-tutorial/tree/master/code). All the code is organized by chapter, the folder name is the chapter number.
## Exercises
-There are few exercises At the end of each chapter of the book. It is for testing whether you can use the knowledge points in the current chapter. You can find the possible answer to the problem from [here](../../exercise). The folder name is the chapter number.
+There are few exercises At the end of each chapter of the book. It is for testing whether you can use the knowledge points in the current chapter. You can find the possible answer to the problem from [here](https://github.com/changkun/modern-cpp-tutorial/tree/master/exercises). The folder name is the chapter number.
[Table of Content](./toc.md) | [Next Chapter: Towards Modern C++](./01-intro.md)
## Licenses
-Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/01-intro.md b/book/en-us/01-intro.md
index e31087bb..03ecd88c 100644
--- a/book/en-us/01-intro.md
+++ b/book/en-us/01-intro.md
@@ -43,7 +43,7 @@ Before learning modern C++, let's take a look at the main features that have dep
- **C language style type conversion is deprecated (ie using `(convert_type)`) before variables, and `static_cast`, `reinterpret_cast`, `const_cast` should be used for type conversion.**
-- **In particular, some of the C standard libraries that can be used are deprecated in the latest C++17 standard, such as ``, ``, `` and `` Wait**
+- **In particular, some of the C standard libraries that can be used are deprecated in the latest C++17 standard, such as ``, ``, `` and `` etc.**
- ... and many more
@@ -146,4 +146,4 @@ Don't worry at the moment, we will come to meet them in our later chapters.
## Licenses
-Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/02-usability.md b/book/en-us/02-usability.md
index 92f041fc..495f4bd7 100644
--- a/book/en-us/02-usability.md
+++ b/book/en-us/02-usability.md
@@ -18,20 +18,23 @@ which refers to the language behavior that occurred before the runtime.
### nullptr
-The purpose of `nullptr` appears to replace `NULL`. In a sense,
-traditional C++ treats `NULL` and `0` as the same thing,
-depending on how the compiler defines NULL,
-and some compilers define NULL as `((void*)0)` Some will define it directly as `0`.
+*(since C++11)*
-C++ **does not allow** to implicitly convert `void *` to other types.
-But if the compiler tries to define `NULL` as `((void*)0)`, then in the following code:
+The purpose of `nullptr` appears to replace `NULL`. There are **null pointer constants** in the C and C++ languages,
+which can be implicitly converted to null pointer value of any pointer type,
+or null member pointer value of any pointer-to-member type in C++.
+`NULL` is provided by the standard library implementation and defined as an implementation-defined null pointer constant.
+In C, some standard libraries defines `NULL` as `((void*)0)` and some define it as `0`.
+
+C++ **does not allow** to implicitly convert `void *` to other types, and thus `((void*)0)` is not a valid implementation
+of `NULL`. If the standard library tries to define `NULL` as `((void*)0)`, then compilation error would occur in the following code:
```cpp
char *ch = NULL;
```
C++ without the `void *` implicit conversion has to define `NULL` as `0`.
-This still creates a new problem. Defining `NULL` to 0 will cause the overloading feature in `C++` to be confusing.
+This still creates a new problem. Defining `NULL` to `0` will cause the overloading feature in `C++` to be confusing.
Consider the following two `foo` functions:
```cpp
@@ -39,9 +42,9 @@ void foo(char*);
void foo(int);
```
-Then the `foo(NULL);` statement will call `foo(int)`, which will cause the code to be counterintuitive.
+Then the behavior of the `foo(NULL);` statement depends on how `NULL` is implemented: when `NULL` is defined as `0` (for example, on MSVC), it calls `foo(int)`, which is counterintuitive; when `NULL` is defined as the GCC/Clang builtin `__null`, the call `foo(NULL)` becomes ambiguous between the `char*` and `int` overloads and fails to compile. In either case, `NULL` does not behave the way a proper null pointer should during overload resolution.
-To solve this problem, C++11 introduced the `nullptr` keyword, which is specifically used to distinguish null pointers, 0. The type of `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.
+To solve this problem, C++11 introduced the `nullptr` keyword, which is specifically used to distinguish null pointers, `0`. The type of `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.
You can try to compile the following code using clang++:
@@ -92,6 +95,8 @@ We will discuss them in detail later in the [decltype](#decltype) section.
### constexpr
+*(since C++11; relaxed in C++14)*
+
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
@@ -146,14 +151,12 @@ 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 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.
+> Note that some compilers (e.g. GCC, Clang) have compiler extensions enabled by default, supporting a C feature called "[variable-length arrays](https://en.cppreference.com/w/c/language/array#Variable-length_arrays)", which allows defining an array whose length is a non-constant expression and causes the above commented out illegal code to be compilable. To disable the extension, add the compilation option [`-pedantic-errors`](https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-pedantic-errors) (available for both GCC and Clang).
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.
In addition, the function of `constexpr` can use recursion:
@@ -189,6 +192,8 @@ constexpr int fibonacci(const int n) {
### if-switch
+*(since C++17)*
+
In traditional C++, the declaration of 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.
@@ -202,29 +207,32 @@ E.g:
int main() {
std::vector vec = {1, 2, 3, 4};
- // since c++17, can be simplified by using `auto`
+ // before C++17, can be simplified by using `auto`
const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 2);
if (itr != vec.end()) {
*itr = 3;
}
- if (const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 3);
- itr != vec.end()) {
- *itr = 4;
+ // need to define a new variable
+ const std::vector::iterator itr2 = std::find(vec.begin(), vec.end(), 3);
+ if (itr2 != vec.end()) {
+ *itr2 = 4;
}
- // should output: 1, 4, 3, 4. can be simplified using `auto`
- for (std::vector::iterator element = vec.begin(); element != vec.end(); ++element)
+ // will output: 1, 4, 3, 4; can be simplified using `auto`
+ for (std::vector::iterator element = vec.begin(); element != vec.end();
+ ++element)
std::cout << *element << std::endl; } ``` In the above code, we can see that the `itr` variable is defined in the scope of the entire `main()`, which causes us to rename the other when a variable need to traverse -the entire `std::vectors` again. C++17 eliminates this limitation so that +the entire `std::vector` again. C++17 eliminates this limitation so that we can do this in if(or switch): ```cpp +// put the temporary variable into the if-statement if (const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 3);
itr != vec.end()) {
*itr = 4;
@@ -235,6 +243,8 @@ Is it similar to the Go?
### Initializer list
+*(since C++11)*
+
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,
@@ -285,6 +295,8 @@ such as:
```cpp
#include
#include
+#include
+
class MagicFoo {
public:
std::vector vec;
@@ -299,7 +311,9 @@ int main() {
MagicFoo magicFoo = {1, 2, 3, 4, 5};
std::cout << "magicFoo: "; - for (std::vector::iterator it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) std::cout << *it << std::endl; + for (std::vector::iterator it = magicFoo.vec.begin();
+ it != magicFoo.vec.end(); ++it)
+ std::cout << *it << std::endl; } ``` @@ -312,7 +326,8 @@ be used as a formal parameter of a normal function, for example: ```Cpp public: void foo(std::initializer_list list) {
- for (std::initializer_list::iterator it = list.begin(); it != list.end(); ++it) vec.push_back(*it);
+ for (std::initializer_list::iterator it = list.begin();
+ it != list.end(); ++it) vec.push_back(*it);
}
magicFoo.foo({6,7,8,9});
@@ -326,18 +341,11 @@ Foo foo2 {3, 4};
### Structured binding
-Structured bindings provide functionality 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 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 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.
+*(since C++17)*
+
+Functions frequently need to "return several values at once" — for example, a computed result together with a status flag. In traditional C++ this is not elegant: we either define a dedicated struct for it, or pack the values into a `std::tuple` and return that, but getting the values back out is clumsy — unpacking with `std::tie` forces us to declare every variable in advance and to know exactly how many elements the tuple holds and the type of each, and any mismatch is an error.
-C++17 completes this setting,
-and the structured bindings let us write code like this:
+C++17's **structured bindings** exist precisely to remove that clumsiness: they let us, in a single line, "unpack" a tuple, a `std::pair`, a raw array, or a struct with public data members, and bind the pieces directly to a set of named variables, with the types deduced by the compiler:
```cpp
#include
@@ -354,6 +362,19 @@ int main() {
}
```
+Compared with `std::tie`, structured bindings need no prior declaration and no spelled-out types, and they work not only on tuples but also on raw arrays and aggregate structs. This is especially handy when iterating an associative container: we can bind each key/value pair to meaningful names instead of writing `it->first` / `it->second`:
+
+```cpp
+#include
+#include
+#include
+
+std::map scores{{"alice", 90}, {"bob", 80}};
+for (const auto& [name, score] : scores) {
+ std::cout << name << ": " << score << '\n'; +} +``` + The `auto` type derivation is described in the [auto type inference](#auto) section. @@ -365,6 +386,8 @@ C++11 introduces the two keywords `auto` and `decltype` to implement type deriva ### auto +*(since C++11)* + `auto` has been in C++ for a long time, but it always exists as an indicator of a storage type, coexisting with `register`. In traditional C++, if 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 of the most common and notable examples of type derivation using `auto` is the iterator. You should see the lengthy iterative writing in traditional C++ in the previous section: @@ -372,8 +395,8 @@ One of the most common and notable examples of type derivation using `auto` is t ```cpp // before C++11 // cbegin() returns vector::const_iterator
-// and therefore itr is type vector::const_iterator
-for(vector::const_iterator it = vec.cbegin(); itr != vec.cend(); ++it)
+// and therefore it is type vector::const_iterator
+for(vector::const_iterator it = vec.cbegin(); it != vec.cend(); ++it)
```
When we have `auto`:
@@ -411,19 +434,26 @@ auto i = 5; // i as int
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);
->
-> 2.6.auto.cpp:16:9: error: 'auto' not allowed in function prototype
-> int add(auto x, auto y) {
-> ^~~~
-> ```
->
-> In addition, `auto` cannot be used to derive array types:
+Since C++ 14, `auto` can even be used as function arguments in generic lambda expressions,
+and such functionality is generalized to normal functions in C++ 20.
+Consider the following example:
+
+```cpp
+auto add14 = [](auto x, auto y) -> int {
+ return x+y;
+}
+
+int add20(auto x, auto y) {
+ return x+y;
+}
+
+auto i = 5; // type int
+auto j = 6; // type int
+std::cout << add14(i, j) << std::endl; +std::cout << add20(i, j) << std::endl; +``` + +> **Note**: `auto` cannot be used to derive array types yet:
>
> ```cpp
> auto auto_arr2[10] = {arr}; // illegal, can't infer array type
@@ -434,8 +464,12 @@ auto arr = new auto(10); // arr as int *
### decltype
-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`:
+*(since C++11)*
+
+The `decltype` keyword is used to solve the defect that the `auto` keyword
+can only deduce the type of a variable. Its usage is very similar to `typeof`,
+a non-standard extension long provided by some compilers (e.g. GCC and Clang)
+that was eventually standardized in C23 but has never been part of standard C++:
```cpp
decltype(expression)
@@ -473,16 +507,18 @@ type z == type x
### tail type inference
-You may think that when we introduce `auto`, we have already mentioned that `auto` cannot be used for function arguments for type derivation. Can `auto` be used to derive the return type of a function? Still consider an example of an add function, which we have to write in traditional C++:
+*(since C++11)*
+
+You may think that whether `auto` can be used to deduce the return type of a function. Still consider an example of an add function, which we have to write in traditional C++:
```cpp
template
R add(T x, U y) {
- return x+y
+ return x+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.
+> 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](https://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 very ugly because the programmer must explicitly
indicate the return type when using this template function.
@@ -537,11 +573,13 @@ std::cout << "q: " << q << std::endl; ### decltype(auto) +*(since C++14)* + `decltype(auto)` is a slightly more complicated use of C++14.> To understand it you need to know the concept of parameter forwarding
> in C++, which we will cover in detail in the
-> [Language Runtime Hardening](./03-runtime.md) chapter,
+> [Language Runtime Enhancements](./03-runtime.md) chapter,
> and you can come back to the contents of this section later.
In simple terms, `decltype(auto)` is mainly used to derive
@@ -582,6 +620,8 @@ decltype(auto) look_up_a_string_2() {
### if constexpr
+*(since C++17)*
+
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
@@ -618,6 +658,8 @@ int main() {
### Range-based for loop
+*(since C++11)*
+
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:
@@ -639,12 +681,36 @@ int main() {
}
```
+A range-based for loop is essentially syntactic sugar. The compiler expands
+
+```cpp
+for (range_declaration : range_expression) loop_statement
+```
+
+roughly into (since C++17):
+
+```cpp
+{
+ auto && __range = range_expression;
+ auto __begin = begin_expr; // __range for an array; __range.begin() or begin(__range) for a class
+ auto __end = end_expr; // __range + N for an array; __range.end() or end(__range) for a class
+ for (; __begin != __end; ++__begin) {
+ range_declaration = *__begin;
+ loop_statement
+ }
+}
+```
+
+Therefore, any type that provides usable `begin()` and `end()` (member functions, or free functions found via ADL) whose returned iterators support `!=`, dereference `*`, and pre-increment `++` can be traversed by a range-based for loop — which is exactly how you make a custom container work with it.
+
## 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, 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
+*(since C++11)*
+
In traditional C++, templates are instantiated by the compiler only when they are used. In other words, as long as a fully defined template is encountered in the code compiled in each compilation unit (file), 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 of the template.
To this end, C++11 introduces an external template that extends the syntax of the original mandatory compiler to instantiate a template at a specific location, allowing us to explicitly tell the compiler when to instantiate the template:
@@ -656,6 +722,8 @@ extern template class std::vector; // should not instantiation in curren
### The ">"
+*(since C++11)*
+
In the traditional C++ compiler, `>>` is always treated as a right shift operator. But actually we can easily write the code for the nested template:
```cpp
@@ -679,6 +747,8 @@ std::vector2)>> magic; // legal, but not recommended
### Type alias templates
+*(since C++11)*
+
Before you understand the type alias template, you need to understand the difference between "template" and "type". Carefully understand this sentence: **Templates are used to generate types.** In traditional C++, `typedef` can define a new name for the type, but there is no way to define a new name for the template. Because the template is not a type. E.g:
```cpp
@@ -711,6 +781,8 @@ int main() {
### Variadic templates
+*(since C++11)*
+
The template has always been one of C++'s unique **Black Magic**.
In traditional C++,
both a class template and a function template could only accept
@@ -830,6 +902,8 @@ To avoid compiler warnings, we can explicitly convert `std::initializer_list` to
### Fold expression
+*(since C++17)*
+
In C++ 17, this feature of the variable length parameter is further brought to the expression, consider the following example:
```cpp
@@ -845,6 +919,8 @@ int main() {
### Non-type template parameter deduction
+*(since C++17)*
+
What we mainly mentioned above is a form of template parameters: type template parameters.
```cpp
@@ -866,7 +942,7 @@ public:
void free(T& item);
private:
T data[BufSize];
-}
+};
buffer_t buf; // 100 as template parameter
```
@@ -889,12 +965,47 @@ int main() {
}
```
+### SFINAE and `std::enable_if`
+
+*(since C++11)*
+
+SFINAE stands for "Substitution Failure Is Not An Error". It describes the rule that, when substituting template arguments produces an invalid type or expression in the **immediate context**, the compiler does not raise an error but silently removes that candidate from the overload set. This was the main way to constrain template parameters before C++20's concepts.
+
+The most common tool is `std::enable_if` from ``. The `describe` below is visible only for integral types:
+
+```cpp
+#include
+
+template >>
+void describe(T) {
+ std::cout << "integral" << std::endl; +} + +describe(42); // OK +// describe(3.14); // compile error: floating point does not satisfy the constraint +``` + +Another common form is **expression SFINAE**, which uses `decltype` to probe whether an expression is valid, thereby detecting at compile time whether a type has a certain capability: + +```cpp +// participates only if c.size() is a valid expression +template
+auto has_size(const T& c) -> decltype(c.size(), std::true_type{}) {
+ return std::true_type{};
+}
+std::false_type has_size(...) { return std::false_type{}; }
+```
+
+SFINAE is powerful but obscure to write and produces verbose error messages. C++20's [concepts](./10-cpp20.md#concept) were introduced precisely to express such constraints in a more intuitive and readable way, and can be regarded as the modern replacement for SFINAE.
+
## 2.6 Object-oriented
### Delegate constructor
-C++11 introduces the concept of a delegate construct, which allows a constructor to call another constructor
-in a constructor in the same class, thus simplifying the code:
+*(since C++11)*
+
+A class often has several constructors that share a lot of the same initialization logic. Before C++11, the only ways to reuse that logic were to copy it into every constructor, or to extract a private init function — but the latter cannot initialize `const` members or reference members. C++11's **delegating constructors** let one constructor delegate its initialization to another constructor of the same class, removing that duplication:
```cpp
#include
@@ -919,7 +1030,9 @@ int main() {
### Inheritance constructor
-In traditional C++, constructors need to pass arguments one by one if they need inheritance, which leads to inefficiency. C++11 introduces the concept of inheritance constructors using the keyword using:
+*(since C++11)*
+
+In traditional C++, if a derived class wants to reuse its base class's constructors, it must redeclare each of them and forward the arguments one by one to the base, which is tedious and error-prone. C++11 introduces inheriting constructors via the `using` keyword, letting a derived class inherit all of the base class's constructors at once:
```cpp
#include
@@ -947,6 +1060,8 @@ int main() {
### Explicit virtual function overwrite
+*(since C++11)*
+
In traditional C++, it is often prone to accidentally overloading virtual functions. E.g:
```cpp
@@ -964,7 +1079,7 @@ C++11 introduces the two keywords `override` and `final` to prevent this from ha
### override
-When overriding a virtual function, introducing the `override` keyword will explicitly tell the compiler to overload, and the compiler will check if the base function has such a virtual function, otherwise it will not compile:
+When overriding a virtual function, introducing the `override` keyword will explicitly tell the compiler to overload, and the compiler will check if the base function has such a virtual function with consistent function signature, otherwise it will not compile:
```cpp
struct Base {
@@ -998,6 +1113,8 @@ struct SubClass3: Base {
### Explicit delete default function
+*(since C++11)*
+
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.
@@ -1017,6 +1134,8 @@ class Magic {
### Strongly typed enumerations
+*(since C++11)*
+
In traditional C++, enumerated types are not type-safe, and enumerated types are treated as integers, which allows two completely different enumerated types to be directly compared (although the compiler gives the check, but not all), ** Even the enumeration value names of different 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 of `enum class`:
@@ -1045,7 +1164,9 @@ And we want to get the value of the enumeration value, we will have to explicitl
```cpp
#include
template
-std::ostream& operator<<(typename std::enable_if::value, std::ostream>::type& stream, const T& e)
+std::ostream& operator<<( + typename std::enable_if::value,
+ std::ostream>::type& stream, const T& e)
{
return stream << static_cast::type>(e);
}
@@ -1057,6 +1178,117 @@ At this point, the following code will be able to be compiled:
std::cout << new_enum::value3 << std::endl ``` +## 2.7 Other Language Features + +### Inline variables + +*(since C++17)* + +Before C++17, a non-const static data member of a class had to be defined separately outside the class, and defining a global variable in a header would cause duplicate-definition link errors when the header was included by multiple translation units. C++17 introduces `inline` variables, which allow a variable (including a static data member) to be defined in a header without violating the One Definition Rule (ODR), even when included by multiple translation units: + +```cpp +struct Widget { + static inline int count = 0; // C++17: define and initialize a static member in-class +}; +inline int global_value = 42; // safe to place in a header +``` + +This greatly simplifies writing header-only libraries. + +### Nested namespace definitions + +*(since C++17)* + +C++17 allows writing nested namespace definitions in a single line using `::`, instead of indenting level by level: + +```cpp +// before C++17 +namespace A { + namespace B { + namespace C { + int value; + } + } +} + +// since C++17 +namespace A::B::C { + int value; +} +``` + +### constexpr lambda + +*(since C++17)* + +Since C++17, a lambda expression that satisfies the requirements of a constant expression is implicitly `constexpr` (and may also be explicitly marked `constexpr`), so it can be evaluated at compile time: + +```cpp +constexpr auto add = [](int a, int b) { return a + b; }; +static_assert(add(1, 2) == 3, ""); + +constexpr int result = add(3, 4); // evaluated at compile time, result == 7 +``` + +### Single-argument static_assert + +*(since C++17)* + +`static_assert` performs a compile-time assertion. Before C++17 it required a diagnostic message as its second argument; since C++17 that message is optional: + +```cpp +static_assert(sizeof(int)>= 2); // C++17: message optional
+static_assert(sizeof(int)>= 2, "int must be>= 2 bytes"); // a message is still allowed
+```
+
+### New aggregate rules
+
+*(since C++17)*
+
+C++17 relaxed the definition of an aggregate: an aggregate may now have public base classes (which must themselves be aggregates), and the base subobject can be brace-initialized along with the rest:
+
+```cpp
+struct Base { int a; };
+struct Derived : Base { int b; };
+
+Derived d{{1}, 2}; // {a}, b — legal since C++17
+```
+
+### Boolean logic metafunctions
+
+*(since C++17)*
+
+C++17 added `std::conjunction`, `std::disjunction`, and `std::negation` to `` for composing other type traits with logical AND/OR/NOT at compile time (and `conjunction`/`disjunction` short-circuit):
+
+```cpp
+#include
+
+template
+constexpr bool is_signed_integral =
+ std::conjunction_v, std::is_signed>;
+
+static_assert(is_signed_integral);
+static_assert(!is_signed_integral);
+static_assert(std::negation_v>);
+```
+
+### `__has_include`
+
+*(since C++17)*
+
+C++17 standardized the preprocessor operator `__has_include`, which checks at compile time whether a header is available, enabling portable conditional inclusion:
+
+```cpp
+#if __has_include()
+# include
+# define HAS_OPTIONAL 1
+#else
+# define HAS_OPTIONAL 0
+#endif
+```
+
+This is very useful when supporting different standard-library versions, or providing a fallback when a feature might be missing.
+
## Conclusion
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:
@@ -1101,4 +1333,4 @@ This section introduces the enhancements to language usability in modern C++, wh
## Licenses
-Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/03-runtime.md b/book/en-us/03-runtime.md
index f7a71c97..1a76a31f 100644
--- a/book/en-us/03-runtime.md
+++ b/book/en-us/03-runtime.md
@@ -16,6 +16,8 @@ So anonymous functions are almost standard in modern programming languages.
### Basics
+*(since C++11)*
+
The basic syntax of a Lambda expression is as follows:
```
@@ -87,8 +89,8 @@ capture lists can be:
- \[\] empty capture list
- \[name1, name2, ...\] captures a series of variables
-- \[&\] reference capture, let the compiler deduce the reference list by itself
-- \[=\] value capture, let the compiler deduce the value list by itself
+- \[&\] reference capture, determine the reference capture list from the uses the in function body
+- \[=\] value capture, determine the value capture list from the uses in the function body
#### 4. Expression capture
@@ -105,6 +107,10 @@ The type of the captured variable being declared is judged according to the expr
and the judgment is the same as using `auto`:
```cpp
+#include
+#include // std::make_unique
+#include // std::move
+
void lambda_expression_capture() {
auto important = std::make_unique(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
@@ -120,15 +126,13 @@ initialize it in the expression.
### Generic Lambda
+*(since C++14)*
+
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,
-and the parameter table type must be clarified.
-
-Fortunately, this trouble only exists in C++11, starting with C++14.
-The formal parameters of the Lambda function can use the `auto` keyword
-to generate generic meanings:
+But lambda expressions are not regular functions, without further specification on the typed parameter list, lambda expressions cannot utilize templates. Fortunately, this trouble
+only exists in C++11, starting with C++14. The formal parameters of the lambda function
+can use the `auto` keyword to utilize template generics:
```cpp
void lambda_generic() {
@@ -149,6 +153,10 @@ This part of the content is also very important, so put it here for the introduc
### `std::function`
+*(since C++11)*
+
+In C++, "things that can be called" come in many shapes — ordinary functions, function pointers, lambda expressions, and any object that overloads `operator()` — and they all have different types, which makes them hard to store and pass around uniformly. `std::function` exists precisely to solve this: it is a type-safe "container for callables" that can uniformly store, copy, and invoke any callable target, letting us handle "functions" as ordinary objects.
+
The essence of a Lambda expression is an object of a class type (called a closure type)
that is similar to a function object type (called a closure object).
When the capture list of a Lambda expression is empty, the closure object
@@ -206,6 +214,8 @@ int main() {
### `std::bind` and `std::placeholder`
+*(since C++11)*
+
And `std::bind` is used to bind the parameters of the function call.
It solves the requirement that we may not always be able to get all the parameters
of a function at one time. Through this function, we can Part of the call parameters
@@ -214,14 +224,14 @@ and then complete the call after the parameters are complete. e.g:
```cpp
int foo(int a, int b, int c) {
- ;
+ return a + b + c;
}
int main() {
- // bind parameter 1, 2 on function foo, and use std::placeholders::_1 as placeholder
- // for the first parameter.
- auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2);
+ // bind parameter 1, 2 on function foo,
+ // and use std::placeholders::_1 as placeholder for the first parameter.
+ auto bindFoo = std::bind(foo, std::placeholders::_1, 1, 2);
// when call bindFoo, we only need one param left
- bindFoo(1);
+ std::cout << bindFoo(1) << std::endl; // outputs 4 } ``` @@ -241,6 +251,13 @@ and making the function object container `std::function` possible. To understand what the rvalue reference is all about, you must have a clear understanding of the lvalue and the rvalue. +> Strictly speaking, **a value category is a property of an *expression*, not of an
+> object**: every C++ expression belongs to exactly one of the three primary value
+> categories — lvalue, prvalue (pure rvalue), and xvalue (expiring value). The phrasings
+> below ("an lvalue is a persistent object", "an rvalue is a temporary object") are
+> intuitive approximations meant to help beginners; for the rigorous definitions, see
+> [cppreference: value category](https://en.cppreference.com/w/cpp/language/value_category).
+
**lvalue, left value**, as the name implies, is the value to the left of the assignment
symbol. To be precise, an lvalue is a persistent object that still exists after
an expression (not necessarily an assignment expression).
@@ -252,29 +269,41 @@ In C++11, in order to introduce powerful rvalue references,
the concept of rvalue values is further divided into:
prvalue, and xvalue.
-**pvalue, pure rvalue**, purely rvalue, either purely literal,
+**prvalue, pure rvalue**, purely rvalue, either purely literal,
such as `10`, `true`; either the result of the evaluation is equivalent to
a literal or anonymous temporary object, for example `1+2`.
Temporary variables returned by non-references, temporary variables generated
by operation expressions, original literals, and Lambda expressions
are all pure rvalue values.
-Note that a string literal became rvalue in a class, and remains an lvalue in other cases (e.g., in a function):
+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 following examples:
```cpp
-class Foo {
- const char*&& right = "this is a rvalue";
-public:
- void bar() {
- right = "still rvalue"; // the string literal is a rvalue
- }
-};
+#include
int main() {
- const char* const &left = "this is an lvalue"; // the string literal is an lvalue
+ // Correct. The type of "01234" is const char [6], so it is an lvalue
+ const char (&left)[6] = "01234";
+
+ // Assert success. It is a const char [6] indeed. Note that decltype(expr)
+ // yields lvalue reference if expr is an lvalue and neither an unparenthesized
+ // id-expression nor an unparenthesized class member access expression.
+ static_assert(std::is_same::value, "");
+
+ // Error. "01234" is an lvalue, which cannot be referenced by an rvalue reference
+ // const char (&&right)[6] = "01234";
}
```
+However, an array can be implicitly converted to a corresponding pointer.The result, if not an lvalue reference, is an rvalue (xvalue if the result is an rvalue reference, prvalue otherwise):
+
+```cpp
+const char* p = "01234"; // Correct. "01234" is implicitly converted to const char*
+const char*&& pr = "01234"; // Correct. "01234" is implicitly converted to const char*, which is a prvalue.
+// const char*& pl = "01234"; // Error. There is no type const char* lvalue
+```
+
**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 concepts),
a value that is destroyed but can be moved.
@@ -312,6 +341,8 @@ This is the move semantics we will mention later.
### rvalue reference and lvalue reference
+*(since C++11)*
+
To get a xvalue, you need to use the declaration of the rvalue reference: `T &&`,
where `T` is the type.
The statement of the rvalue reference extends the lifecycle of this temporary value,
@@ -335,13 +366,14 @@ void reference(std::string&& str) {
int main()
{
std::string lv1 = "string,"; // lv1 is a lvalue
- // std::string&& r1 = s1; // illegal, rvalue can't ref to 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, - const std::string& lv2 = lv1 + lv1; // legal, const lvalue reference can extend temp variable's lifecycle + 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 + std::cout << lv2 << std::endl; // string,string, std::string&& rv2 = lv1 + lv2; // legal, rvalue ref extend lifecycle rv2 += "string"; // legal, non-const reference can be modified @@ -370,7 +402,7 @@ int main() { } ``` -The first question, why not allow non-linear references to bind to non-lvalues? +The first question, why not allow non-constant references to bind to non-lvalues? This is because there is a logic error in this approach: ```cpp @@ -393,6 +425,8 @@ The reason is simple because Fortran needs it. ### Move semantics +*(since C++11)* + Traditional C++ has designed the concept of copy/copy for class objects through copy constructors and assignment operators, but to implement the movement of resources, @@ -466,8 +500,9 @@ int main() { // "str: Hello world." std::cout << "str: " << str << std::endl; - // use push_back(const T&&), no copy - // the string will be moved to vector, and therefore std::move can reduce copy cost + // use push_back(const T&&), + // no copy the string will be moved to vector, + // and therefore std::move can reduce copy cost v.push_back(std::move(str)); // str is empty now std::cout << "str: " << str << std::endl; @@ -478,6 +513,8 @@ int main() { ### Perfect forwarding +*(since C++11)* + As we mentioned earlier, the rvalue reference of a declaration is actually an lvalue. This creates problems for us to parameterize (pass): @@ -525,7 +562,7 @@ both lvalue and rvalue. But follow the rules below: | T&& | rvalue ref | T&& | 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. +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` successful delivery of lvalues. Perfect forwarding is based on the above rules. The so-called perfect forwarding is to let us pass the parameters, @@ -582,7 +619,7 @@ static_cast 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,
@@ -616,6 +653,32 @@ It can be seen that the principle of `std::forward` is to make clever use of the
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.
+### Guaranteed copy elision
+
+*(since C++17)*
+
+Before C++17, when an object was initialized from a prvalue of the same type, the compiler *was permitted* (but not required) to omit the copy/move construction — this is known as copy elision. Because it was merely allowed, the object's type still had to have an accessible copy or move constructor, even though it would not actually be called.
+
+C++17 makes copy elision **guaranteed** in this case: when an object is initialized from a prvalue of the same type, there is no temporary object at all — the object is constructed directly in its target location. As a result, returning a prvalue by value is well-formed even for a type that is neither copyable nor movable:
+
+```cpp
+struct NonMovable {
+ NonMovable() = default;
+ NonMovable(const NonMovable&) = delete; // non-copyable
+ NonMovable(NonMovable&&) = delete; // non-movable
+};
+
+NonMovable make() {
+ return NonMovable{}; // OK in C++17: guaranteed elision, no copy/move needed
+}
+
+int main() {
+ NonMovable n = make(); // constructed directly into n
+}
+```
+
+The code above would not compile before C++17 (an accessible move or copy constructor was required), but is well-formed in C++17. This lets factory functions safely return non-movable types.
+
## Conclusion
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:
@@ -633,4 +696,4 @@ Lambda expression
## Licenses
-Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/04-containers.md b/book/en-us/04-containers.md
index 96842464..15d0a1fd 100644
--- a/book/en-us/04-containers.md
+++ b/book/en-us/04-containers.md
@@ -1,10 +1,10 @@
---
-title: "Chapter 04 Containers"
+title: "Chapter 04: Containers"
type: book-en-us
order: 4
---
-# Chapter 04 Containers
+# Chapter 04: Containers
[TOC]
@@ -12,6 +12,8 @@ order: 4
### `std::array`
+*(since C++11)*
+
When you see this container, you will have this problem:
1. Why introduce `std::array` instead of `std::vector` directly?
@@ -103,6 +105,8 @@ std::sort(arr.begin(), arr.end());
### `std::forward_list`
+*(since C++11)*
+
`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.
@@ -111,6 +115,8 @@ It is also the only container in the standard library container that does not pr
## 4.2 Unordered Container
+*(since C++11)*
+
We are already familiar with the ordered container `std::map`/`std::set` in traditional C++. These elements are internally implemented by red-black trees.
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. @@ -118,7 +124,7 @@ And select the appropriate location to insert into the container. When traversin 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 +C++11 introduces two unordered containers: `std::unordered_map`/`std::unordered_multimap` and `std::unordered_set`/`std::unordered_multiset`. Their usage is basically similar to the original `std::map`/`std::multimap`/`std::set`/`set::multiset` @@ -177,6 +183,8 @@ But the flaw of `std::pair` is obvious, only two elements can be saved. ### Basic Operations +*(since C++11)* + There are three core functions for the use of tuples: 1. `std::make_tuple`: construct tuple @@ -295,6 +303,123 @@ for(int i = 0; i != tuple_len(new_tuple); ++i) std::cout << tuple_index(new_tuple, i) << std::endl; ``` +That said, traversing a tuple by "first implementing runtime indexing, then indexing element by element" works but is rather roundabout. If all you want is to apply the same operation to every element, the more direct and idiomatic way is to expand the indices at compile time with `std::index_sequence` (introduced in C++14). In C++17, it can be combined with a fold expression: + +```cpp +template
+void iterate_impl(Func&& f, Tuple&& tpl, std::index_sequence) {
+ (f(std::get(std::forward(tpl))), ...);
+}
+template
+void iterate_tuple(Func&& f, Tuple&& tpl) {
+ iterate_impl(std::forward(f), std::forward(tpl),
+ std::make_index_sequence>>{});
+}
+```
+
+In C++20, we can further drop the helper function by using a lambda that allows explicitly written template parameters:
+
+```cpp
+template
+void iterate_tuple(Func f, const std::tuple& tpl) {
+ [&](std::index_sequence) {
+ (f(std::get(tpl)), ...);
+ }(std::make_index_sequence());
+}
+```
+
+The call site is then very straightforward, and no runtime indexing is needed beforehand:
+
+```cpp
+iterate_tuple([](const auto& v) { std::cout << v << ' '; }, new_tuple); +``` + +## 4.4 `std::string_view` and `std::byte` + +### `std::string_view` + +*(since C++17)* + +`std::string_view`, introduced in C++17, is a **non-owning, read-only** view over a sequence of characters; it holds just a pointer and a length. Declaring a parameter as `std::string_view` accepts both a `std::string` and a string literal, **without any copy or allocation**: + +```cpp +#include
+
+void print(std::string_view sv) {
+ std::cout << sv << " (size = " << sv.size() << ")" << std::endl; +} + +std::string_view sv = "hello, world"; +print(sv.substr(0, 5)); // "hello"; substr does not allocate + +std::string s = "from std::string"; +print(s); // implicit conversion, no copy +``` + +Mind its **lifetime**: a `string_view` does not own the underlying data, so the referenced character sequence must outlive the view, otherwise you get a dangling reference. + +### `std::byte` + +*(since C++17)* + +`std::byte` represents a single byte of **raw memory**. Unlike `char` or `unsigned char`, it is not an arithmetic type — the standard defines only bitwise operators for it, which prevents accidental arithmetic on raw bytes at the type level: + +```cpp +#include
+
+std::byte b{0b0000'1100}; // 12
+b <<= 2; // 48 +b |= std::byte{0b0000'0001}; // 49 +int v = std::to_integer(b); // explicit conversion to an integer: 49
+```
+
+## 4.5 Associative container improvements
+
+*(since C++17)*
+
+C++17 added several more precise and more efficient operations to associative containers such as `std::map` / `std::unordered_map`:
+
+- `try_emplace`: inserts only when the key is absent; when the key already exists it does **not** modify the existing value nor move from its arguments, which makes it better than `emplace` for "insert if not present".
+- `insert_or_assign`: inserts a new element, or **overwrites** the value when the key already exists, returning whether an insertion happened.
+- Node-based operations `extract` / `merge`: `extract` detaches a node from the container without copying or moving the element, and `merge` splices nodes from another container directly in.
+
+```cpp
+#include
+#include
+
+std::map m;
+m.try_emplace(1, "one");
+m.try_emplace(1, "uno"); // no effect, key 1 already present
+m.insert_or_assign(1, "ONE"); // overwrites with "ONE"
+
+std::map other;
+other.insert(m.extract(1)); // move node 1 into other, no element copy
+
+std::map more{{3, "three"}};
+m.merge(more); // splice more's nodes into m
+```
+
+## 4.6 Polymorphic allocators `std::pmr`
+
+*(since C++17)*
+
+C++17 introduced the `std::pmr` namespace in ``, providing polymorphic allocators based on a **memory resource**. It decouples the *where to allocate* policy from the container type: the same `pmr` container backed by different memory resources is still a single type, avoiding the type bloat caused by template allocators.
+
+For instance, `std::pmr::monotonic_buffer_resource` can allocate from a pre-prepared buffer (even one on the stack) and frees everything only when the resource is destroyed — ideal for allocation-heavy workloads with a shared lifetime:
+
+```cpp
+#include
+#include
+#include
+#include
+
+std::array buffer;
+std::pmr::monotonic_buffer_resource pool{buffer.data(), buffer.size()};
+
+std::pmr::vector v{&pool}; // allocates from the stack buffer, not the heap
+for (int i = 0; i < 5; ++i) v.push_back(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, to get better performance. @@ -305,4 +430,4 @@ Although `std::tuple` is effective, the standard library provides limited functi ## Licenses -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/05-pointers.md b/book/en-us/05-pointers.md
index 90c83b41..e496d98e 100644
--- a/book/en-us/05-pointers.md
+++ b/book/en-us/05-pointers.md
@@ -1,10 +1,10 @@
---
-title: "Chapter 05 Smart Pointers and Memory Management"
+title: "Chapter 05: Smart Pointers and Memory Management"
type: book-en-us
order: 5
---
-# Chapter 05 Smart Pointers and Memory Management
+# Chapter 05: Smart Pointers and Memory Management
[TOC]
@@ -26,6 +26,8 @@ These smart pointers include `std::shared_ptr`/`std::unique_ptr`/`std::weak_ptr`
## 5.2 `std::shared_ptr`
+*(since C++11)*
+
`std::shared_ptr` is a smart pointer that records how many `shared_ptr` points to an object, eliminating to call `delete`, which automatically deletes the object when the reference 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 of asymmetry.
@@ -36,12 +38,10 @@ And return the `std::shared_ptr` pointer of this object type. For example:
```cpp
#include
#include
-void foo(std::shared_ptr i)
-{
+void foo(std::shared_ptr i) {
(*i)++;
}
-int main()
-{
+int main() {
// auto pointer = new int(10); // illegal, no direct assignment
// Constructed a std::shared_ptr
auto pointer = std::make_shared(10);
@@ -59,29 +59,37 @@ And see the reference count of an object by `use_count()`. E.g:
auto pointer = std::make_shared(10);
auto pointer2 = pointer; // reference count+1
auto pointer3 = pointer; // reference count+1
-int *p = pointer.get(); // no increase of reference count
-std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3 +int *p = pointer.get(); // no increase of reference count + +std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3 std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3 std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3 pointer2.reset(); std::cout << "reset pointer2:" << std::endl; -std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2 -std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 has reset + +std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2 +std::cout << "pointer2.use_count() = " + << pointer2.use_count() << std::endl; // pointer2 has reset, 0 std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2 + pointer3.reset(); std::cout << "reset pointer3:" << std::endl; -std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1 + +std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1 std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0 -std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0, pointer3 has reset +std::cout << "pointer3.use_count() = " + << pointer3.use_count() << std::endl; // pointer3 has reset, 0 ``` ## 5.3 `std::unique_ptr` -`std::unique_ptr` is an exclusive smart pointer that prohibits other smart pointers from sharing the same object, thus keeping the code safe: +*(since C++11)* + +`std::unique_ptr` is an exclusive smart pointer that prohibits other smart pointers from sharing the same object by copy construction or copy assignment, thus avoiding errors due to repeated destruction or freeing: ```cpp -std::unique_ptr pointer = std::make_unique(10); // make_unique was introduced in C++14
+std::unique_ptr pointer = std::make_unique(10); // make_unique, from C++14
std::unique_ptr pointer2 = pointer; // illegal
```
@@ -144,6 +152,8 @@ int main() {
## 5.4 `std::weak_ptr`
+*(since C++11)*
+
If you think about `std::shared_ptr` carefully, you will still find that there is still a problem that resources cannot be released. Look at the following example:
```cpp
@@ -187,7 +197,8 @@ 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 implemented `*` and `->` operators, therefore it cannot operate on resources. `std::weak_ptr` allows us to check if a `std::shared_ptr` exists or not. The `expired()` method of a `std::weak_ptr` returns `false` when the resource is not released; Otherwise, it returns `true`.
+Furthermore, it can also be used for the purpose of 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
@@ -197,8 +208,8 @@ The technology of smart pointers is not novel. It is a common technology in many
## Further Readings
-- [Why does C++11 have `make_shared` but not `make_unique`](http://stackoverflow.com/questions/12580432/why-does-c11-have-make-shared-but-not-make-unique)
+- [Why does C++11 have `make_shared` but not `make_unique`](https://stackoverflow.com/questions/12580432/why-does-c11-have-make-shared-but-not-make-unique)
## Licenses
-Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/06-regex.md b/book/en-us/06-regex.md
index 5793645a..a41e6522 100644
--- a/book/en-us/06-regex.md
+++ b/book/en-us/06-regex.md
@@ -1,10 +1,10 @@
---
-title: "Chapter 06 Regular Expression"
+title: "Chapter 06: Regular Expression"
type: book-en-us
order: 6
---
-# Chapter 06 Regular Expression
+# Chapter 06: Regular Expression
[TOC]
@@ -34,37 +34,39 @@ and lowercase letters, all numbers, all punctuation, and some other symbols.
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. |
-| `+` | Matches the previous subexpression one or more times. |
-| `.` | Matches any single character except the newline character `\n`. |
-| `[` | Marks the beginning of a bracket expression. |
-| `?` | Matches the previous subexpression zero or one time, or indicates a non-greedy qualifier. |
-| `\` | Marks the next character as either a special character, or a literal character, or a backward reference, or an octal escape character. For example, `n` Matches the character `n`. `\n` matches newline characters. The sequence `\\` Matches the `'\'` character, while `\(` matches the `'('` character. |
-| `^` | Matches the beginning of the input string, unless it is used in a square bracket expression, at which point it indicates that the set of characters is not accepted. |
-| `{` | Marks the beginning of a qualifier expression. |
-| `\|` | Indicates a choice between the two. |
+| Symbol | 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. |
+| `+` | Matches the previous subexpression one or more times.|
+| `.` | Matches any single character except the newline character `\n`.|
+| `[` | Marks the beginning of a bracket expression.|
+| `?` | Matches the previous subexpression zero or one time, or indicates a non-greedy qualifier.|
+| `\` | Marks the next character as either a special character, or a literal character, or a backward reference, or an octal escape character. For example, `n` Matches the character `n`. `\n` matches newline characters. The sequence `\\` Matches the `'\'` character, while `\(` matches the `'('` character. |
+| `^` | Matches the beginning of the input string, unless it is used in a square bracket expression, at which point it indicates that the set of characters is not accepted.|
+| `{` | Marks the beginning of a qualifier expression.|
+| `\|` | Indicates a choice between the two.|
### Quantifiers
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}`. |
-| `{n}` | `n` is a non-negative integer. Matches the determined `n` times. For example, `o{2}` cannot match `o` in `for`, but can match two `o` in `foo`. |
-| `{n,}` | `n` is a non-negative integer. Match at least `n` times. For example, `o{2,}` cannot match `o` in `for`, but matches all `o` in `foooooo`. `o{1,}` is equivalent to `o+`. `o{0,}` is equivalent to `o*`. |
-| `{n,m}` | `m` and `n` are non-negative integers, where `n` is less than or equal to `m`. Matches at least `n` times and matches up to `m` times. For example, `o{1,3}` will match the first three `o` in `foooooo`. `o{0,1}` is equivalent to `o?`. Note that there can be no spaces between the comma and the two numbers. |
+| Symbol | 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}`.|
+| `{n}` | `n` is a non-negative integer. Matches the determined `n` times. For example, `o{2}` cannot match `o` in `for`, but can match two `o` in `foo`.|
+| `{n,}` | `n` is a non-negative integer. Match at least `n` times. For example, `o{2,}` cannot match `o` in `for`, but matches all `o` in `foooooo`. `o{1,}` is equivalent to `o+`. `o{0,}` is equivalent to `o*`.|
+| `{n,m}` | `m` and `n` are non-negative integers, where `n` is less than or equal to `m`. Matches at least `n` times and matches up to `m` times. For example, `o{1,3}` will match the first three `o` in `foooooo`. `o{0,1}` is equivalent to `o?`. Note that there can be no spaces between the comma and the two numbers. |
With these two tables, we can usually read almost all regular expressions.
## 6.2 `std::regex` and Its Related
+*(since C++11)*
+
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.
@@ -84,7 +86,9 @@ We use a simple example to briefly introduce the use of this library. Consider t
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 `\\.`
+ // 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; @@ -103,7 +107,8 @@ 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 + // 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; @@ -186,16 +191,23 @@ Please implement the member functions `start()` and `parse_request`. Enable serv template
void start_server(SERVER_TYPE &server) {
- // process GET request for /match/[digit+numbers], e.g. GET request is /match/abc123, will return abc123
- server.resource["fill_your_reg_ex"]["GET"] = [](ostream& response, Request& request) {
+ // process GET request for /match/[digit+numbers],
+ // e.g. GET request is /match/abc123, will return abc123
+ 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; + response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() + << "\r\n\r\n" << number; }; - // peocess default GET request; anonymous function will be called if no other matches - // response files in folder web/ + // peocess default GET request; + // anonymous function will be called + // if no other matches response files in folder web/ // default: index.html - server.default_resource["fill_your_reg_ex"]["GET"] = [](ostream& response, Request& request) { + server.default_resource["fill_your_reg_ex"]["GET"] = + [](ostream& response, Request& request) + { string filename = "www/"; string path = request.path_match[1]; @@ -223,9 +235,9 @@ An suggested solution can be found [here](../../exercises/6). ## Further Readings -1. [Comments from `std::regex`'s author](http://zhihu.com/question/23070203/answer/84248248) -2. [Library document of Regular Expression](http://en.cppreference.com/w/cpp/regex) +1. [Comments from `std::regex`'s author](https://zhihu.com/question/23070203/answer/84248248) +2. [Library document of Regular Expression](https://en.cppreference.com/w/cpp/regex) ## Licenses -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/07-thread.md b/book/en-us/07-thread.md
index 412e7905..6ece91cc 100644
--- a/book/en-us/07-thread.md
+++ b/book/en-us/07-thread.md
@@ -1,15 +1,19 @@
---
-title: "Chapter 07 Parallelism and Concurrency"
+title: "Chapter 07: Parallelism and Concurrency"
type: book-en-us
order: 7
---
-# Chapter 07 Parallelism and Concurrency
+# Chapter 07: Parallelism and Concurrency
[TOC]
## 7.1 Basic of Parallelism
+*(since C++11)*
+
+Before C++11, the C++ standard library had no notion of threads at all: writing concurrent code meant reaching for platform-specific APIs such as POSIX `pthread` or the Windows thread API, which made that code hard to port. C++11 brought threading into the language standard for the first time, so the same concurrent code now builds across platforms.
+
`std::thread` is used to create an execution thread instance, so it is the basis for all concurrent programming. It needs to include the `` header file when using it.
It provides a number of basic thread operations, such as `get_id()` to get the thread ID of the thread being created, use `join()` to join a thread, etc., for example:
@@ -28,19 +32,22 @@ int main() {
## 7.2 Mutex and Critical Section
+*(since C++11)*
+
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 `` header file.
-`std::mutex` is the most basic `mutex` class in C++11, and you can create a mutex by instantiating `std::mutex`.
+`std::mutex` is the most basic mutex class in C++11, and a mutex can be created by constructing a `std::mutex` object.
It can be locked by its member function `lock()`, and `unlock()` can be unlocked.
But in the process of actually writing the code, it is best not to directly call the member function,
Because calling member functions, you need to call `unlock()` at the exit of each critical section, and of course, exceptions.
-At this time, C++11 also provides a template class `std::lock_guard` for the RAII syntax for the mutex.
+At this time, C++11 also provides a template class `std::lock_guard` for the RAII mechanism for the mutex.
RAII guarantees the exceptional security of the code while keeping the simplicity of the code.
```cpp
#include
+#include
#include
int v = 1;
@@ -66,9 +73,11 @@ int main() {
```
Because C++ guarantees that all stack objects will be destroyed at the end of the declaration period, such code is also extremely safe.
-Whether `critical_section()` returns normally or if an exception is thrown in the middle, a stack rollback is thrown, and `unlock()` is automatically called.
+Whether `critical_section()` returns normally or if an exception is thrown in the middle, a stack unwinding is thrown, and `unlock()` is automatically called.
+
+> An exception is thrown and not caught (it is implementation-defined whether any stack unwinding is done in this case).
-And `std::unique_lock` is more flexible than `std::lock_guard`, `std::unique_lock` is more flexible.
+`std::unique_lock` is more flexible than `std::lock_guard`.
Objects of `std::unique_lock` manage the locking and unlocking operations on the `mutex` object with exclusive ownership (no other `unique_lock` objects owning the ownership of a `mutex` object). So in concurrent programming, it is recommended to use `std::unique_lock`.
`std::lock_guard` cannot explicitly call `lock` and `unlock`, and `std::unique_lock` can be called anywhere after the declaration.
@@ -80,6 +89,7 @@ For instance:
```cpp
#include
+#include
#include
int v = 1;
@@ -113,6 +123,8 @@ int main() {
## 7.3 Future
+*(since C++11)*
+
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.
To understand this feature, we need to understand the multi-threaded behavior before C++11.
@@ -143,7 +155,8 @@ int main() {
std::cout << "waiting..."; result.wait(); // block until future has arrived // output result - std::cout << "done!" << std:: endl << "future result is " << result.get() << std::endl; + std::cout << "done!" << std:: endl << "future result is " + << result.get() << std::endl; return 0; } ``` @@ -152,10 +165,12 @@ After encapsulating the target to be called, you can use `get_future()` to get a ## 7.4 Condition Variable +*(since C++11)* + The condition variable `std::condition_variable` was born to solve the deadlock and was introduced when the mutex operation was not enough. For example, a thread may need to wait for a condition to be true to continue execution. A dead wait loop can cause all other threads to fail to enter the critical section so that when the condition is true, a deadlock occurs. -Therefore, the `condition_variable` instance is created primarily to wake up the waiting thread and avoid deadlocks. +Therefore, the `condition_variable` object is created primarily to wake up the waiting thread and avoid deadlocks. `notify_one()` of `std::condition_variable` is used to wake up a thread; `notify_all()` is to notify all threads. Below is an example of a producer and consumer model: @@ -194,7 +209,8 @@ int main() { // temporal unlock to allow producer produces more rather than // let consumer hold the lock until its consumed. lock.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // consumer is slower + // consumer is slower + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); lock.lock(); if (!produced_nums.empty()) { std::cout << "consuming " << produced_nums.front() << std::endl; @@ -223,8 +239,10 @@ We simply can't expect multiple consumers to be able to produce content in a par ## 7.5 Atomic Operation and Memory Model +*(since C++11)* + 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. +For example, the compiler may have optimizations for the variable `notified`, 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 the memory model introduced from C++11. Let's first look at a question. What is the output of the following code? ```cpp @@ -253,7 +271,7 @@ 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. +Intuitively, it seems that `a = 5;` in `t2` always executes before `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 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. 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. @@ -270,8 +288,8 @@ This is a very strong set of synchronization conditions, in other words when it 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. -Therefore, in the C + + 11 multi-threaded shared variable reading and writing, the introduction of the `std::atomic` template, so that we instantiate an atomic type, will be a -Atomic type read and write operations are minimized from a set of instructions to a single CPU instruction. E.g: +Therefore, the `std::atomic` template is introduced in C++11 for the topic of multi-threaded shared variable reading and writing, which enables us to instantiate atomic types, +and minimize an atomic read or write operation from a set of instructions to a single CPU instruction. E.g: ```cpp std::atomic counter;
@@ -322,7 +340,7 @@ int main() {
}
```
-### Concistency Model
+### 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 of time, and even unreliable communication occurs.
@@ -366,7 +384,7 @@ Weakening the synchronization conditions between processes, usually we will cons
x.store(2)
```
- 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, ie As long as `x.store(2)` of `T2` occurs before `x.store(3)`.
+ 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)` of `T2` occurs before `x.store(3)`.
3. Causal consistency: its requirements are further reduced, only the sequence of causal operations is guaranteed, and the order of non-causal operations is not required.
@@ -415,8 +433,11 @@ Weakening the synchronization conditions between processes, usually we will cons
```
3 4 4 4 // The write operation of x was quickly observed
0 3 3 4 // There is a delay in the observed time of the x write operation
- 0 0 0 4 // The last read read the final value of x, but the previous changes were not observed.
- 0 0 0 0 // The write operation of x is not observed in the current time period, but the situation that x is 4 can be observed at some point in the future.
+ 0 0 0 4 // The last read read the final value of x,
+ // but the previous changes were not observed.
+ 0 0 0 0 // The write operation of x is not observed in the current time period,
+ // but the situation that x is 4 can be observed
+ // at some point in the future.
```
### Memory Orders
@@ -440,7 +461,7 @@ To achieve the ultimate performance and achieve consistency of various strength
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: +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 on the 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 // initialize as nullptr to prevent consumer load a dangling pointer @@ -464,7 +485,8 @@ To achieve the ultimate performance and achieve consistency of various strength 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, 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 a write before a release does not occur after the release operation, which is a **backward barrier**, and `std::memory_order_acquire` ensures that a subsequent read or write after a acquire does not occur before the acquire operation, which is a **forward barrier**. + For the `std::memory_order_acq_rel` option, combines the characteristics of the two barriers and determines a unique memory barrier, such that reads and writes of the current thread will not be rearranged across the barrier. Let's check an example: @@ -477,9 +499,8 @@ To achieve the ultimate performance and achieve consistency of various strength }); std::thread acqrel([&]() { int expected = 1; // must before compare_exchange_strong - while(!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) { + while(!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) expected = 1; // must after compare_exchange_strong - } // flag has changed to 2 }); std::thread acquire([&]() { @@ -492,7 +513,7 @@ To achieve the ultimate performance and achieve consistency of various strength 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`. However, in most cases, `compare_exchange_weak` is discouraged due to the complexity of its usage. 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: @@ -541,10 +562,10 @@ They provide a critical foundation for standardized high-performance computing f ## Further Readings -- [C++ 并发编程(中文版)](https://www.amazon.com/dp/1617294691/ref=cm_sw_em_r_mt_dp_U_siEmDbRMMF960) -- [Thread document](http://en.cppreference.com/w/cpp/thread) +- [C++ Concurrency in Action](https://www.amazon.com/dp/1617294691/ref=cm_sw_em_r_mt_dp_U_siEmDbRMMF960) +- [Thread document](https://en.cppreference.com/w/cpp/thread) - Herlihy, M. P., & Wing, J. M. (1990). Linearizability: a correctness condition for concurrent objects. ACM Transactions on Programming Languages and Systems, 12(3), 463–492. https://doi.org/10.1145/78969.78972 ## Licenses -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).`
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).`
diff --git a/book/en-us/08-filesystem.md b/book/en-us/08-filesystem.md
index d0c86838..c8de9b09 100644
--- a/book/en-us/08-filesystem.md
+++ b/book/en-us/08-filesystem.md
@@ -1,25 +1,164 @@
---
-title: "Chapter 08 File System"
+title: "Chapter 08: File System"
type: book-en-us
order: 8
---
-# Chapter 08 File System
+# Chapter 08: File System
[TOC]
-The file system library provides functions related to
-the operation of the file system, path, regular files, directories, and so on.
-Similar to the regular expression library, he was one of the first libraries
-to be launched by boost and eventually merged into the C++ standard.
+The file system library provides functions related to
+the operation of the file system, path, regular files, directories, and so on.
+Similar to the regular expression library, it was one of the first libraries
+to be launched by boost and eventually merged into the C++ standard in C++17.
+Its entire contents live in the `` header under the
+`std::filesystem` namespace, for which a short alias is conventionally introduced:
-## 8.1 Document and Link
+```cpp
+#include
+namespace fs = std::filesystem;
+```
-TODO:
+> On some older toolchains (e.g. GCC before version 8), using the filesystem
+> library also required linking against `-lstdc++fs` (or LLVM's `-lc++fs`);
+> modern compilers no longer need this.
-## 8.2 std::filesystem
+## 8.1 The path `std::filesystem::path`
-TODO:
+`std::filesystem::path` is the heart of the library. It represents a file path in a
+portable way and hides the differences between operating systems in their path
+separators (such as `/` and `\`). A `path` can be constructed from a string and
+composed with `operator/`:
+
+```cpp
+fs::path p = "/usr/local";
+p /= "bin"; // p is now /usr/local/bin
+fs::path q = p / "clang"; // composes without modifying p
+```
+
+It is worth emphasizing that a `path` is only a **syntactic** representation of a
+path: constructing one neither touches the disk nor requires the path to actually
+exist. It provides a set of member functions for decomposing a path:
+
+```cpp
+fs::path p = "/usr/local/hello.txt";
+p.filename(); // "hello.txt"
+p.stem(); // "hello"
+p.extension(); // ".txt"
+p.parent_path(); // "/usr/local"
+```
+
+## 8.2 Querying file status
+
+The library provides a set of **non-member functions** for querying the actual
+file a path refers to; each takes a `path` argument:
+
+```cpp
+fs::exists(p); // does the path exist
+fs::is_regular_file(p); // is it a regular file
+fs::is_directory(p); // is it a directory
+fs::file_size(p); // file size in bytes
+fs::last_write_time(p); // last modification time
+```
+
+Note that these operations, which genuinely access the file system, **throw a
+`std::filesystem::filesystem_error`** on failure (e.g. the path does not exist or
+permission is denied). For nearly every such function the library provides an
+overload taking a `std::error_code&`, to report errors without exceptions:
+
+```cpp
+std::error_code ec;
+auto size = fs::file_size(p, ec); // does not throw on error; writes to ec
+if (ec) {
+ std::cout << "cannot get size: " << ec.message() << std::endl; +} +``` + +## 8.3 Iterating directories + +`std::filesystem::directory_iterator` iterates over the immediate entries of a +directory, while `recursive_directory_iterator` walks the entire directory tree. +Both can be used directly in a range-based for loop, yielding `directory_entry` +elements: + +```cpp +for (const auto& entry : fs::directory_iterator(dir)) { + std::cout << entry.path() << std::endl; +} + +// recursively walk the whole tree +for (const auto& entry : fs::recursive_directory_iterator(dir)) { + if (entry.is_regular_file()) + std::cout << entry.path() << " (" << entry.file_size() << ")\n"; +} +``` + +A `directory_entry` caches the status of the file it refers to, so member functions +such as `entry.is_regular_file()` and `entry.file_size()` are often more efficient +than calling the non-member functions again on its path. + +## 8.4 Creating, copying, and removing + +The library also offers a set of operations that modify the file system: + +```cpp +fs::create_directories(p / "a" / "b"); // create directories recursively +fs::copy_file(src, dst); // copy a single file +fs::copy(src, dst, fs::copy_options::recursive); // copy a directory recursively +fs::rename(old_path, new_path); // rename / move +fs::remove(p); // remove a single file or empty directory +fs::remove_all(p); // remove recursively, returns the count removed +``` + +## 8.5 A complete example + +The following example combines the operations above. To keep it self-contained and +repeatable, we create a dedicated working directory under the system temporary +directory and clean it up at the end: + +```cpp +#include
+#include
+#include
+
+namespace fs = std::filesystem;
+
+int main() {
+ // Work in a private scratch directory so the example is
+ // self-contained and repeatable.
+ const fs::path base = fs::temp_directory_path() / "modern-cpp-fs-demo";
+ fs::remove_all(base); // clean up any previous run
+ fs::create_directories(base / "sub"); // creates intermediate dirs
+
+ // Create a file.
+ std::ofstream(base / "hello.txt") << "hello, filesystem"; + + // Path decomposition (no filesystem access required). + const fs::path p = base / "hello.txt"; + std::cout << "filename: " << p.filename() << "\n"; + std::cout << "extension: " << p.extension() << "\n"; + std::cout << "parent: " << p.parent_path() << "\n"; + + // Query the file. + std::cout << "exists: " << fs::exists(p) << "\n"; + std::cout << "is_regular_file: " << fs::is_regular_file(p) << "\n"; + std::cout << "file_size: " << fs::file_size(p) << "\n"; + + // Recursively iterate the directory tree. + std::cout << "entries:\n"; + for (const auto& entry : fs::recursive_directory_iterator(base)) + std::cout << " " << entry.path() << "\n"; + + // Copy, then rename. + fs::copy_file(p, base / "copy.txt"); + fs::rename(base / "copy.txt", base / "renamed.txt"); + + // Clean up. + fs::remove_all(base); + std::cout << "after cleanup, exists: " << fs::exists(base) << "\n"; +} +``` [Table of Content](./toc.md) | [Previous Chapter](./07-thread.md) | [Next Chapter: Minor Features](./09-others.md) @@ -27,4 +166,4 @@ TODO: ## Licenses -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
\ No newline at end of file
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
\ No newline at end of file
diff --git a/book/en-us/09-others.md b/book/en-us/09-others.md
index 9db078ef..fdfa008a 100644
--- a/book/en-us/09-others.md
+++ b/book/en-us/09-others.md
@@ -1,15 +1,17 @@
---
-title: Chapter 09 Minor Features
+title: "Chapter 09: Minor Features"
type: book-en-us
order: 9
---
-# Chapter 09 Minor Features
+# Chapter 09: Minor Features
[TOC]
## 9.1 New Type
+*(since C++11)*
+
### `long long int`
`long long int` is not the first to be introduced in C++11.
@@ -20,6 +22,8 @@ specifying a `long long int` type with at least 64 bits.
## 9.2 `noexcept` and Its Operations
+*(since C++11)*
+
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.
@@ -39,9 +43,9 @@ void may_throw(); // May throw any exception
void no_throw() noexcept; // Cannot throw any exception
```
-If a function modified with `noexcept` is thrown,
-the compiler will use `std::terminate()` to
-immediately terminate the program.
+If a function marked `noexcept` performs an operation that possibly throws an exception,
+the compiler will insert a `std::terminate()` call to the path exiting the function via an exception,
+in order to make the program terminate.
`noexcept` can also be used as an operator to manipulate an expression.
When the expression has no exception, it returns `true`,
@@ -104,6 +108,8 @@ exception captured, from non_block_throw()
## 9.3 Literal
+*(since C++11)*
+
### Raw String Literal
In traditional C++, it is very painful to write a string full of
@@ -159,6 +165,8 @@ Custom literals support four literals:
## 9.4 Memory Alignment
+*(since C++11)*
+
C++ 11 introduces two new keywords, `alignof` and `alignas`, to support control of memory alignment.
The `alignof` keyword can get a platform-dependent value of type `std::size_t` to query the alignment of the platform.
Of course, we are sometimes not satisfied with this, and even want to customize the alignment of the structure. Similarly, C++ 11 introduces `alignas`.
@@ -191,6 +199,61 @@ int main() {
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.
+### Dynamic allocation of over-aligned types
+
+Before C++17, a `new` expression could not guarantee the alignment requirement of an **over-aligned** type (one whose alignment exceeds `alignof(std::max_align_t)`); using such types often required platform-specific facilities such as `posix_memalign` or `_aligned_malloc`. C++17 introduced `operator new` / `operator delete` overloads taking a `std::align_val_t`, so a `new` expression automatically selects the aligned version when allocating an over-aligned type:
+
+```cpp
+struct alignas(64) Aligned {
+ double v[8];
+};
+
+Aligned* p = new Aligned; // C++17: automatically uses the aligned operator new
+// now reinterpret_cast(p) % 64 == 0
+delete p;
+```
+
+## 9.5 Type punning and `std::bit_cast`
+
+*(since C++20)*
+
+"Type punning" means reinterpreting the same memory as a different type, common in low-level code (e.g. reading the bit pattern of a floating-point number). Many people reach for `reinterpret_cast` through a pointer or reference:
+
+```cpp
+float f = 3.14f;
+std::uint32_t bits = *reinterpret_cast(&f); // undefined behavior!
+```
+
+But this violates the **strict-aliasing rule**: except through `char`, `unsigned char`, or `std::byte`, accessing an object via an lvalue of a type incompatible with the object's actual type is undefined behavior, and the optimizer is free to assume it never happens.
+
+The correct, portable approach is `std::memcpy` (valid under any standard):
+
+```cpp
+std::uint32_t bits;
+std::memcpy(&bits, &f, sizeof bits); // well-defined
+```
+
+C++20 further provides `std::bit_cast` (in ``), which reinterprets the object representation in a well-defined way with clearer semantics and can be used in constant expressions:
+
+```cpp
+#include
+auto bits = std::bit_cast(f); // both types must be the same size and trivially copyable
+float back = std::bit_cast(bits);
+```
+
+## 9.6 Mathematical special functions
+
+*(since C++17)*
+
+C++17 added a set of mathematical special functions to `` — such as `std::riemann_zeta`, `std::beta`, `std::assoc_legendre`, and `std::cyl_bessel_j` — useful in scientific computing and machine-learning related domains:
+
+```cpp
+#include
+double z = std::riemann_zeta(2.0); // ~ 1.6449 (i.e. pi^2 / 6)
+```
+
+> Note that although these special functions are part of the standard, **standard-library support varies**: libstdc++ (GCC) provides a complete implementation, while libc++ (Clang) did not implement them for a long time. The code above therefore may not compile on every toolchain; check your standard library's support before using them.
+
## Conclusion
Several of the features introduced in this section are those that
@@ -199,8 +262,8 @@ have not yet been introduced. `noexcept` is the most important feature.
One of its features is to prevent the spread of anomalies,
effective Let the compiler optimize our code to the maximum extent possible.
-[Table of Content](./toc.md) | [Previous Chapter](./08-filesystem.md) | [Next Chapter: Outlook: Introduction of C++20](./10-cpp20.md)
+[Table of Content](./toc.md) | [Previous Chapter](./08-filesystem.md) | [Next Chapter: C++20](./10-cpp20.md)
## Licenses
-Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/10-cpp20.md b/book/en-us/10-cpp20.md
index 8988edf3..75ac2f86 100644
--- a/book/en-us/10-cpp20.md
+++ b/book/en-us/10-cpp20.md
@@ -1,10 +1,10 @@
---
-title: "Chapter 10 Outlook: Introduction of C++20"
+title: "Chapter 10: C++20"
type: book-en-us
order: 10
---
-# Chapter 10 Outlook: Introduction of C++20
+# Chapter 10: C++20
[TOC]
@@ -64,39 +64,146 @@ Even use it directly as a type:
void sort(Sortable& c); // c is a Sortable type object
```
-Let's look at a practical example.
+Let's look at a practical example. The following uses a `requires` expression to define a concept `Addable`, requiring that a type support `+` with a result convertible back to the type, and uses it to constrain a function template:
-TODO:
+```cpp
+#include
+#include
+
+// Concept: T must support a + b, with a result convertible to T
+template
+concept Addable = requires(T a, T b) {
+ { a + b } -> std::convertible_to;
+};
-## Module
+template
+T sum(T a, T b) { return a + b; }
-TODO:
+int main() {
+ std::cout << sum(1, 2) << std::endl; // 3 + std::cout << sum(1.5, 2.5) << std::endl; // 4 + // sum("a", "b"); // compile error: does not satisfy Addable, with a clear message +} +``` + +When an argument of an unsatisfying type is passed, the compiler tells us directly that the constraint is not satisfied, instead of emitting a long cascade of internal template-instantiation errors. + +## Modules + +Modules aim to solve the many problems of the traditional header mechanism: repeated parsing, macro pollution, include-order sensitivity, and slow compilation. A module is declared with `export module` and explicitly `export`s the entities visible to the outside: + +```cpp +// math.cppm — module interface unit +export module math; + +export int add(int a, int b) { + return a + b; +} +``` + +Consumers use `import` instead of `#include`: + +```cpp +// main.cpp +import math; +import ;
+
+int main() {
+ std::cout << add(1, 2) << std::endl; +} +``` -## Contract +> Unlike the other examples in this book, compiling modules requires dedicated toolchain support and usually two steps (compile the module interface unit first, then the consumer); it cannot be built with a single `clang++ file.cpp` command. Consult your compiler's documentation for the exact build procedure.
-TODO:
+## Ranges
+
+Ranges provide a higher-level, composable abstraction over the standard-library algorithms and iterators. With **range adaptors** and the pipe operator `|`, several lazy transformations can be chained in a declarative style:
+
+```cpp
+#include
+#include
+#include
+
+int main() {
+ std::vector v{1, 2, 3, 4, 5, 6};
+ auto result = v | std::views::filter([](int x) { return x % 2 == 0; })
+ | std::views::transform([](int x) { return x * x; });
+ for (int x : result) std::cout << x << ' '; // 4 16 36 + std::cout << std::endl; +} +``` + +These views are **lazily evaluated**: the filtering and transformation are computed element by element only when `result` is iterated, with no intermediate container created. + +## Coroutines + +A coroutine is a function that can be suspended and resumed. Any function whose body uses `co_await`, `co_yield`, or `co_return` is a coroutine. Note that C++20 provides only the **language machinery** plus the low-level support facilities in ``, leaving the "glue" such as the `promise_type` to the user or a library (a ready-made `std::generator` only arrived in C++23).
+
+Here is a minimal lazy generator that yields values one at a time with `co_yield`:
+
+```cpp
+#include
+#include
+#include
+
+template
+struct Generator {
+ struct promise_type {
+ T current;
+ Generator get_return_object() { return Generator{handle::from_promise(*this)}; }
+ std::suspend_always initial_suspend() { return {}; }
+ std::suspend_always final_suspend() noexcept { return {}; }
+ std::suspend_always yield_value(T value) { current = value; return {}; }
+ void return_void() {}
+ void unhandled_exception() { std::terminate(); }
+ };
+ using handle = std::coroutine_handle;
+ handle h;
+ explicit Generator(handle h) : h(h) {}
+ ~Generator() { if (h) h.destroy(); }
+ Generator(const Generator&) = delete;
+ Generator(Generator&& o) noexcept : h(o.h) { o.h = {}; }
+ std::optional next() {
+ if (!h || h.done()) return std::nullopt;
+ h.resume();
+ if (h.done()) return std::nullopt;
+ return h.promise().current;
+ }
+};
+
+Generator range(int a, int b) {
+ for (int i = a; i < b; ++i) co_yield i; +} + +int main() { + auto g = range(1, 5); + while (auto v = g.next()) std::cout << *v << ' '; // 1 2 3 4 + std::cout << std::endl; +} +``` -## Range +## A note on Contracts and Transactional Memory -TODO: +A common misconception is worth clarifying: **Contracts** and **Transactional Memory** are *not* part of C++20. -## Coroutine +- Contracts were once in the C++20 working draft but were removed before the standard was published; they are now an important feature targeting C++26 (see this repository's C++26 tracking issue #318). +- Transactional Memory exists only as a Technical Specification (TS) and was never merged into the C++20 standard. -TODO: +This chapter therefore no longer presents them as C++20 features. ## Conclusion In general, I finally saw the exciting features of Concepts/Ranges/Modules in C++20. This is still full of charm for a programming language that is already in its thirties. -[Table of Content](./toc.md) | [Previous Chapter](./09-others.md) | [Next Chapter](./appendix1.md) +[Table of Content](./toc.md) | [Previous Chapter](./09-others.md) | [Next Chapter: Introduction of C++23](./11-cpp23.md) ## Further Readings - [Why Concepts didn't make C++17?](http://honermann.net/blog/2016/03/06/why-concepts-didnt-make-cxx17/) -- [C++11/14/17/20 Compiler Support](http://en.cppreference.com/w/cpp/compiler_support) +- [Modern C++ Compiler Support](https://en.cppreference.com/w/cpp/compiler_support) - [C++ History](https://en.cppreference.com/w/cpp/language/history) ## Licenses -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/11-cpp23.md b/book/en-us/11-cpp23.md
new file mode 100644
index 00000000..f848f2ba
--- /dev/null
+++ b/book/en-us/11-cpp23.md
@@ -0,0 +1,195 @@
+---
+title: "Chapter 11: C++23"
+type: book-en-us
+order: 11
+---
+
+# Chapter 11: C++23
+
+[TOC]
+
+If C++20 was another "major" release in the spirit of C++11, C++23 is more of an incremental update focused on **consolidation and polish**: it completes some features that were rushed into C++20 and adds many long-requested library facilities. This chapter introduces a selection of its more important and practical features.
+
+> The examples in this chapter use C++23 features and must be compiled with `-std=c++2b` (or `-std=c++23`). In addition, depending on the progress of each standard-library implementation, some library features (such as `std::generator`, `std::move_only_function`, ``, and `import std;`) may not yet be available on your toolchain; this is noted where relevant.
+
+## 11.1 Language features
+
+### Deducing `this` (explicit object parameter)
+
+Before C++23, making a member function serve `const`/non-`const` and lvalue/rvalue cases at once usually meant writing several nearly identical overloads. C++23 introduces an **explicit object parameter**, allowing `this` to be written as the function's first parameter and its cv-/ref-qualification to be deduced via a template, so a single function template covers every case:
+
+```cpp
+struct Counter {
+ int value = 0;
+ template
+ auto&& get(this Self&& self) { // self is the former *this
+ return self.value;
+ }
+};
+```
+
+This also makes previously awkward patterns, such as recursive lambdas, natural to write.
+
+### `if consteval`
+
+C++23 introduces `if consteval`, which distinguishes within a function body whether the current context is **constant evaluation**, letting you choose different implementations for compile time and run time:
+
+```cpp
+constexpr int compute(int x) {
+ if consteval {
+ return x * 2; // taken during constant evaluation
+ } else {
+ return x * 3; // taken at run time
+ }
+}
+```
+
+### Multidimensional subscript operator
+
+C++23 lets `operator[]` take multiple subscript arguments, so multidimensional containers can use the intuitive `m[i, j]` syntax instead of falling back to `operator()`:
+
+```cpp
+struct Matrix2x3 {
+ int data[6] = {};
+ int& operator[](std::size_t r, std::size_t c) { return data[r * 3 + c]; }
+};
+
+Matrix2x3 m;
+m[1, 2] = 7;
+```
+
+### `auto(x)` and static `operator()`
+
+C++23 provides the `auto(x)` / `auto{x}` syntax to explicitly produce a **decay-copy** — a prvalue copy of the same type as `x` — which is handy when you specifically want a copy:
+
+```cpp
+std::vector v{1, 2, 3};
+auto copy = auto(v); // explicit copy, a prvalue
+```
+
+In addition, the call operator of a function object (including a lambda) can now be declared `static`, dropping the implicit object parameter, which can improve performance in some scenarios:
+
+```cpp
+struct Add {
+ static int operator()(int a, int b) { return a + b; }
+};
+```
+
+### `[[assume]]`
+
+`[[assume(expr)]]` is a C++23 standardized attribute that tells the compiler an expression is guaranteed to be true at that point, allowing the optimizer to take advantage of it. Note that if the assumption does not actually hold at run time, the behavior is undefined, so use it with care:
+
+```cpp
+int divide_by(int x) {
+ [[assume(x> 0)]]; // promise the optimizer x is positive
+ return 100 / x;
+}
+```
+
+### Other small language improvements
+
+C++23 also includes several smaller language improvements:
+
+- **Preprocessor directives**: added `#elifdef` / `#elifndef` (shorthands for `#elif defined` / `#elif !defined`) and `#warning` for emitting a diagnostic on purpose.
+- **Named universal character escapes**: a character can be written by its Unicode name, e.g. `"\N{GREEK SMALL LETTER ALPHA}"`, which is more readable than memorizing code points.
+- **Extended floating-point types**: `` introduces fixed-width floating-point types such as `std::float16_t`, `std::float32_t`, `std::float64_t`, and `std::bfloat16_t` (their availability likewise depends on the platform and standard-library implementation).
+- **Further `constexpr` relaxations**: constant evaluation now permits `goto` and labeled statements, as well as declaring (but not using during constant evaluation) variables of non-literal type and `static` variables.
+
+## 11.2 The standard library
+
+### `std::expected`
+
+`std::expected` represents a result that is "either a value `T` or an error `E`", providing a type-safe, expressive way to handle errors without exceptions, similar to the `Result` type in other languages:
+
+```cpp
+#include
+
+std::expected parse_positive(int x) {
+ if (x> 0) return x;
+ return std::unexpected("not positive");
+}
+
+auto r = parse_positive(42);
+if (r) {
+ // use *r
+} else {
+ // use r.error()
+}
+```
+
+### `std::print` and `std::println`
+
+C++23's `` provides `std::print` and `std::println`, which build on C++20's `std::format` to produce output via a type-safe format string — more concise and more efficient than the traditional chained `iostream` `<<`: + +```cpp +#include
+
+std::println("Hello, {}!", "C++23");
+std::println("{} + {} = {}", 1, 2, 1 + 2);
+```
+
+### `std::mdspan`
+
+`std::mdspan` is a **non-owning multidimensional view** over contiguous storage. It does not allocate; it merely reinterprets the layout of an underlying one-dimensional array according to the given extents, and is widely used in scientific and high-performance computing:
+
+```cpp
+#include
+
+std::array storage{1, 2, 3, 4, 5, 6};
+std::mdspan m(storage.data(), 2, 3); // view it as 2 x 3
+int x = m[1, 2]; // together with the multidimensional subscript
+```
+
+### `std::flat_map` and `std::flat_set`
+
+`std::flat_map` / `std::flat_set` are associative containers backed by **sorted contiguous storage** (by default two `vector`s). Compared with the red-black-tree-based `std::map`, insertion is slower, but lookup and iteration are more cache-friendly and the memory overhead is smaller:
+
+```cpp
+#include
+
+std::flat_map m;
+m.insert({3, "three"});
+m.insert({1, "one"});
+// iterated in sorted key order: 1, 3
+```
+
+### Ranges additions
+
+C++23 added many useful range adaptors, such as `views::zip` (iterates several ranges in lockstep, element by element), `views::enumerate` (attaches an index to each element), `views::chunk`, `views::slide`, and `views::join_with`, as well as the general-purpose `std::ranges::to` (materializes a range into a concrete container):
+
+```cpp
+#include
+
+std::vector names{"a", "b", "c"};
+std::vector scores{90, 80, 70};
+for (auto&& [name, score] : std::views::zip(names, scores)) {
+ // name and score correspond one to one
+}
+```
+
+### Other improvements
+
+C++23 also includes many small but practical improvements, for example:
+
+- `std::string` / `std::string_view` gained a `contains` member to directly test for a substring or character;
+- `` added `std::byteswap`, which reverses the byte order of an integer;
+- `std::optional` gained monadic operations such as `and_then`, `transform`, and `or_else`.
+
+## 11.3 A note on library support
+
+Most C++23 language features are already well supported by mainstream compilers, but **the availability of some library features varies by implementation**. For instance, `std::generator` (coroutine ranges), `std::move_only_function`, ``, and the standard-library module `import std;` may not yet be implemented, or may still be experimental, in some standard libraries (especially libc++). Before using these features, check the [cppreference compiler support page](https://en.cppreference.com/w/cpp/compiler_support) to confirm the support status of your toolchain.
+
+## Conclusion
+
+Although C++23 does not bring the disruptive changes of C++11 or C++20, features such as `std::expected`, `std::print`, `std::mdspan`, and the explicit object parameter genuinely improve the day-to-day experience of writing C++. Together with the upcoming C++26, modern C++ continues to evolve.
+
+[Table of Content](./toc.md) | [Previous Chapter](./10-cpp20.md) | [Next Chapter: Introduction of C++26](./12-cpp26.md)
+
+## Further Readings
+
+- [C++23 - cppreference](https://en.cppreference.com/w/cpp/23)
+- [C++ compiler support](https://en.cppreference.com/w/cpp/compiler_support)
+
+## Licenses
+
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/12-cpp26.md b/book/en-us/12-cpp26.md
new file mode 100644
index 00000000..9b93161b
--- /dev/null
+++ b/book/en-us/12-cpp26.md
@@ -0,0 +1,138 @@
+---
+title: "Chapter 12: C++26 (Outlook)"
+type: book-en-us
+order: 12
+---
+
+# Chapter 12: C++26 (Outlook)
+
+[TOC]
+
+> **Important note**: at the time of writing, C++26 is still being **finalized** (its Draft International Standard is expected around 2026), and its contents may still change. Moreover, the vast majority of C++26 features are **not yet implemented, or are still experimental**, in current mainstream compilers and standard libraries. This chapter is therefore a **forward-looking preview**: for features a compiler already supports we give runnable examples; for the headline features that have not yet landed we only describe their intent and approximate syntax. Treat [cppreference: C++26](https://en.cppreference.com/w/cpp/26) as the source of truth.
+
+C++26 is highly anticipated because it is expected to deliver long-incubated major features such as reflection, contracts, and `std::execution`. We introduce them below.
+
+## 12.1 Language features
+
+### Static reflection
+
+Reflection may be the most anticipated feature of C++26. It lets a program inspect and manipulate its own structure (types, members, enumerators, etc.) as first-class values **at compile time**, providing a standardized way to do what previously required macros or external code generators. The design introduces the reflection operator `^^` (which yields a `std::meta::info` value representing an entity) and the "splice" syntax `[: ... :]` (which turns a reflection value back into code):
+
+```cpp
+// Conceptual example (final syntax subject to the standard)
+constexpr auto r = ^^int; // a reflection value representing int
+typename [: r :] x = 42; // splice it back into the type int
+```
+
+> Reflection is not yet available in mainstream toolchains; the code above does not currently compile and only illustrates the shape of the feature.
+
+### Contracts
+
+Contracts add language-level **preconditions**, **postconditions**, and assertions to functions, expressing and checking interface obligations in a tool-recognizable way:
+
+```cpp
+// Conceptual example
+int divide(int a, int b)
+ pre(b != 0) // precondition
+ post(r: r * b <= a) // postcondition (r is the return value) +{ + contract_assert(b != 0); // statement-level assertion + return a / b; +} +``` + +> Contracts were removed during the C++20 cycle and now target C++26. They are likewise not yet available in mainstream toolchains.
+
+### Pack indexing
+
+C++26 lets you index the N-th element of a parameter pack directly with `pack...[N]`, so you no longer need recursion or `std::tuple` unpacking:
+
+```cpp
+template
+auto first(Ts... ts) { return ts...[0]; }
+
+template
+auto last(Ts... ts) { return ts...[sizeof...(ts) - 1]; }
+```
+
+### `= delete` with a reason
+
+C++26 allows a deleted function to carry a reason string, which the compiler reports when that overload is selected, producing friendlier diagnostics:
+
+```cpp
+void use_span(int* p, int n);
+void use_span(std::nullptr_t) = delete("pass a real buffer, not nullptr");
+```
+
+### The placeholder `_`
+
+C++26 makes `_` a **placeholder name**: it may be re-declared in the same scope, clearly expressing "I do not care about this value", for example the parts of a structured binding you do not need:
+
+```cpp
+auto _ = compute(); // I don't care about the result
+auto _ = compute(); // C++26 allows re-declaring _
+```
+
+## 12.2 The standard library
+
+### `std::execution` (senders / receivers)
+
+`std::execution` (proposal P2300, often called the "senders/receivers" model) provides a standardized, composable execution framework for **asynchronous and parallel computation**, and is regarded as one of the cornerstones of modern C++ async programming.
+
+> This framework is not yet widely available in standard-library implementations, so this chapter does not provide a runnable example.
+
+### Saturation arithmetic
+
+C++26 adds **saturation arithmetic** functions `std::add_sat`, `std::sub_sat`, `std::mul_sat`, `std::div_sat`, and `std::saturate_cast` to ``. Unlike ordinary integer arithmetic, which wraps around on overflow, saturating operations **clamp** to the largest or smallest value the type can represent:
+
+```cpp
+#include
+#include
+
+int hi = std::numeric_limits::max();
+std::add_sat(hi, 1); // yields INT_MAX instead of wrapping to a negative value
+std::sub_sat(0, 1); // -1
+```
+
+### Other library facilities
+
+C++26 also plans to bring many new library facilities, including:
+
+- `std::inplace_vector`: a sequence container with fixed capacity whose elements are stored in place (no heap allocation);
+- `std::function_ref`: a lightweight **non-owning** reference to a callable, well suited as a function parameter;
+- `std::simd` (``): portable data-parallel (SIMD) types;
+- the linear algebra library `std::linalg`, `std::text_encoding`, and more.
+
+> Most of these are not yet implemented in current standard libraries; whether a few of them (such as `std::hive`) are ultimately included in C++26 remains uncertain — defer to the final standard and cppreference.
+
+### Other notable proposals
+
+Beyond the features above, C++26 brings many improvements to both the language and the library, for example:
+
+- **Structured binding enhancements**: allowing structured bindings in conditions (`if`/`while`), and introducing packs in structured bindings;
+- **Trivially relocatable types**: letting "move then destroy" be optimized into a single memory copy;
+- **User-generated `static_assert` messages** and **`constexpr` structured bindings**: making compile-time assertion messages producible from constant expressions;
+- **`std::submdspan`** and other enhancements to `std::span` / `std::mdspan` (such as construction from an `initializer_list`);
+- a **hardened standard library** and `std::is_within_lifetime`, improving safety;
+- **debugging support**, such as `std::breakpoint` and `std::is_debugger_present`.
+
+> Again, the final form and inclusion of these proposals are subject to the final standard.
+
+## 12.3 On the standard and implementation status
+
+To reiterate: C++26 has not been officially released, the **final syntax and inclusion of the features described here may change**, and only a small subset (pack indexing, `= delete` with a reason, the placeholder `_`, saturation arithmetic) can currently be compiled and run. Readers are encouraged to follow [cppreference: C++26](https://en.cppreference.com/w/cpp/26) and the [compiler support page](https://en.cppreference.com/w/cpp/compiler_support) for the latest progress.
+
+## Conclusion
+
+C++26 is poised to be another milestone after C++11 and C++20 — if reflection and contracts land as planned, they will once again profoundly change the way we write C++. Until it officially arrives, staying informed and experimenting cautiously is the best preparation.
+
+[Table of Content](./toc.md) | [Previous Chapter](./11-cpp23.md) | [Next Chapter: Further Study Materials](./appendix1.md)
+
+## Further Readings
+
+- [C++26 - cppreference](https://en.cppreference.com/w/cpp/26)
+- [C++ compiler support](https://en.cppreference.com/w/cpp/compiler_support)
+
+## Licenses
+
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/appendix1.md b/book/en-us/appendix1.md
index dd17766b..18308b26 100644
--- a/book/en-us/appendix1.md
+++ b/book/en-us/appendix1.md
@@ -1,22 +1,22 @@
---
title: "Appendix 1: Further Study Materials"
type: book-en-us
-order: 11
+order: 13
---
# Appendix 1: Further Study Materials
First of 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 features of modern C++ 11/14/17/20, rather than the advanced learning practice of C++ "Black Magic". The author of course also thinks about this demand, but the content is very difficult and there are few 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:
+As mentioned in the introduction to this book, this book is just a book that takes you quickly to the new features of modern C++ (C++11 to C++26), rather than the advanced learning practice of C++ "Black Magic". The author of course also thinks about this demand, but the content is very difficult and there are few 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++ Reference](http://en.cppreference.com/w)
+- [C++ Reference](https://en.cppreference.com/w)
- [CppCon YouTube Channel](https://www.youtube.com/user/CppCon/videos)
- [Ulrich Drepper. What Every Programmer Should Know About Memory. 2007](https://people.freebsd.org/~lstewart/articles/cpumemory.pdf)
- to be added
-[Table of Content](./toc.md) | [Previous Chapter](./10-cpp20.md) | [Next Chapter](./appendix2.md)
+[Table of Content](./toc.md) | [Previous Chapter](./12-cpp26.md) | [Next Chapter](./appendix2.md)
## Licenses
-Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
\ No newline at end of file
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
\ No newline at end of file
diff --git a/book/en-us/appendix2.md b/book/en-us/appendix2.md
index 8d377b9c..f0914f5c 100644
--- a/book/en-us/appendix2.md
+++ b/book/en-us/appendix2.md
@@ -1,39 +1,62 @@
---
title: "Appendix 2: Modern C++ Best Practices"
type: book-en-us
-order: 12
+order: 14
---
# Appendix 2: Modern C++ Best Practices
-In this appendix we will briefly talk about the best practices of modern C++. In general, the author's thoughts on C++'s best practices are mainly absorbed from [Effective Modern C++](https://www.amazon.com/dp/1491903996/ref=cm_sw_em_r_mt_dp_U_-ZgjDb81ERBNP) and [C++ Style Guide](https://google.github.io/styleguide/cppguide.html). In this appendix, we will briefly discuss and use the actual examples to illustrate the methods, and introduce some of **the author's personal**, **non-common**, **non-sensible** best practices, and how to ensure the overall quality of the code.
+In this appendix we briefly discuss the best practices of modern C++. Many of these ideas are distilled from [Effective Modern C++](https://www.amazon.com/dp/1491903996/) and the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html), as well as the [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) maintained by Bjarne Stroustrup and Herb Sutter. The goal of this appendix is to summarize widely accepted practices that help ensure the overall quality of your code.
## Common Tools
-TODO:
+Good tooling catches a large class of problems before they ever reach production:
+
+- **Enable warnings, and treat them as errors.** Compile with `-Wall -Wextra` (and consider `-Wpedantic`); `-Werror` keeps warnings from accumulating.
+- **Run sanitizers during testing.** AddressSanitizer (`-fsanitize=address`), UndefinedBehaviorSanitizer (`-fsanitize=undefined`) and ThreadSanitizer (`-fsanitize=thread`) catch memory errors, undefined behavior, and data races at run time.
+- **Format and lint automatically.** `clang-format` keeps style consistent, and `clang-tidy` flags bug-prone patterns and suggests modernizations.
+- **Use a modern build system and package manager**, such as CMake together with vcpkg or Conan, to make builds reproducible.
+- **Experiment quickly** with [Compiler Explorer](https://godbolt.org/) to inspect the generated assembly and compare compilers and standards.
## Coding Style
-TODO:
+A consistent style makes a codebase far easier to read and maintain:
+
+- Pick a style guide and apply it consistently across the whole project; consistency matters more than the specific choices.
+- Be `const`-correct: mark variables, member functions, and parameters `const` whenever they do not need to mutate state.
+- Use `auto` to avoid redundant type spelling, but not at the cost of readability — keep the reader able to tell what a name means.
+- Prefer the standard library (containers, algorithms, `std::string`) over hand-rolled equivalents.
## Overall Performance
-TODO:
+- **Measure before you optimize.** Use a profiler to find the real hot spots instead of guessing; premature optimization wastes effort and harms readability.
+- **Avoid unnecessary copies.** Pass large objects by `const&`, or take by value and `std::move` when you need a copy anyway; `reserve()` containers when the final size is known.
+- **Let the compiler help.** Return local objects by value and rely on (guaranteed) copy elision rather than returning via output parameters.
+- Prefer move semantics for expensive-to-copy types, and remember that `std::move` is a cast, not an operation.
## Code Security
-TODO:
+- **Manage resources with RAII.** Wrap every resource (memory, files, locks, sockets) in an object whose destructor releases it.
+- **Prefer smart pointers** (`std::unique_ptr`, `std::shared_ptr`) over raw `new`/`delete`; avoid owning raw pointers.
+- **Avoid undefined behavior**: no out-of-bounds access, no signed overflow, no use-after-free, no strict-aliasing violations (see §9.5). Sanitizers help detect these.
+- Prefer `std::span`, `std::array`, and `.at()` over raw pointers and unchecked indexing when bounds matter, and avoid C-style casts in favor of the named C++ casts.
## Maintainability
-TODO:
+- Keep functions small and focused on a single responsibility; prefer standard algorithms over hand-written loops to express intent clearly.
+- Make interfaces hard to misuse: use strong types and enum classes instead of bare `int`/`bool` flags.
+- Write tests, and run them continuously so regressions are caught early.
+- Document *why*, not *what*: the code already says what it does.
## Portability
-TODO:
+- Use fixed-width integer types (`std::int32_t`, etc.) from `` when the exact size matters; do not assume `int` is 32 bits or that `char` is signed.
+- Avoid implementation-defined and platform-specific behavior; when you must depend on it, isolate it behind a small abstraction.
+- Prefer the standard library over platform-specific APIs (e.g. ``, ``, ``) so the same code builds across platforms.
+- When byte order matters, query it explicitly (`std::endian` in C++20) rather than assuming little- or big-endian.
-[Table of Content](./toc.md) | [Previous Chapter](./appendix1.md)
+[Table of Content](./toc.md) | [Previous Chapter](./appendix1.md) | [Next Chapter: Modern C++ Feature Index](./appendix3.md)
## Licenses
-Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
\ No newline at end of file
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
\ No newline at end of file
diff --git a/book/en-us/appendix3.md b/book/en-us/appendix3.md
new file mode 100644
index 00000000..10cd1527
--- /dev/null
+++ b/book/en-us/appendix3.md
@@ -0,0 +1,91 @@
+---
+title: "Appendix 3: Modern C++ Feature Index"
+type: book-en-us
+order: 15
+---
+
+# Appendix 3: Modern C++ Feature Index
+
+This appendix groups the main features covered in this book by standard version and notes the chapter where each appears, so you can browse "by version" or quickly locate a feature later. If you are already familiar with the content of a given version, feel free to jump straight to the section you need.
+
+## C++11 / C++14
+
+| Feature | Chapter |
+|:--------|:--------|
+| `nullptr` | Chapter 2 |
+| `constexpr` (relaxed in C++14) | Chapter 2 |
+| Initializer lists (`std::initializer_list`) | Chapter 2 |
+| `auto` type deduction | Chapter 2 |
+| `decltype` / `decltype(auto)` (C++14) | Chapter 2 |
+| Tail return type | Chapter 2 |
+| Range-based for loop | Chapter 2 |
+| External / alias / default / variadic templates | Chapter 2 |
+| Delegating and inheriting constructors | Chapter 2 |
+| Explicit `override` / `final` | Chapter 2 |
+| Explicit `= default` / `= delete` | Chapter 2 |
+| Strongly-typed enums (`enum class`) | Chapter 2 |
+| SFINAE and `std::enable_if` | Chapter 2 |
+| Lambda expressions, generic lambdas (C++14) | Chapter 3 |
+| `std::function`, `std::bind` | Chapter 3 |
+| Rvalue references, move semantics, perfect forwarding | Chapter 3 |
+| `std::array`, `std::forward_list`, unordered containers, `std::tuple` | Chapter 4 |
+| Smart pointers `shared_ptr` / `unique_ptr` / `weak_ptr`, `make_unique` (C++14) | Chapter 5 |
+| Regular expressions (`std::regex`) | Chapter 6 |
+| Threads, mutexes, futures, condition variables, atomics & memory model | Chapter 7 |
+| `long long`, `noexcept`, raw/user-defined literals, memory alignment | Chapter 9 |
+
+## C++17
+
+| Feature | Chapter |
+|:--------|:--------|
+| Structured bindings | Chapter 2 |
+| `if` / `switch` init-statements | Chapter 2 |
+| `if constexpr` | Chapter 2 |
+| Fold expressions, `auto` non-type template parameters | Chapter 2 |
+| Inline variables, nested namespaces, `constexpr` lambda | Chapter 2 |
+| Single-argument `static_assert`, new aggregate rules | Chapter 2 |
+| Boolean logic metafunctions, `__has_include` | Chapter 2 |
+| Guaranteed copy elision | Chapter 3 |
+| `std::string_view`, `std::byte` | Chapter 4 |
+| Associative container improvements (`try_emplace` / `merge` ...), `std::pmr` | Chapter 4 |
+| `std::filesystem` | Chapter 8 |
+| `new` for over-aligned types | Chapter 9 |
+| Mathematical special functions | Chapter 9 |
+
+## C++20
+
+| Feature | Chapter |
+|:--------|:--------|
+| Concepts and constraints | Chapter 10 |
+| Modules | Chapter 10 |
+| Ranges | Chapter 10 |
+| Coroutines | Chapter 10 |
+| `std::bit_cast` | Chapter 9 |
+
+## C++23
+
+| Feature | Chapter |
+|:--------|:--------|
+| Deducing `this` (explicit object parameter) | Chapter 11 |
+| `if consteval`, multidimensional subscript | Chapter 11 |
+| `auto(x)`, static `operator()`, `[[assume]]` | Chapter 11 |
+| `std::expected` | Chapter 11 |
+| `std::print` / `std::println` | Chapter 11 |
+| `std::mdspan`, `std::flat_map` / `std::flat_set` | Chapter 11 |
+| Ranges additions (`zip` ...), `string::contains`, `std::byteswap` | Chapter 11 |
+
+## C++26 (outlook)
+
+| Feature | Chapter |
+|:--------|:--------|
+| Static reflection | Chapter 12 |
+| Contracts | Chapter 12 |
+| Pack indexing, `= delete` with a reason, the placeholder `_` | Chapter 12 |
+| `std::execution` (senders / receivers) | Chapter 12 |
+| Saturation arithmetic | Chapter 12 |
+
+[Table of Content](./toc.md) | [Previous Chapter](./appendix2.md)
+
+## Licenses
+
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
diff --git a/book/en-us/toc.md b/book/en-us/toc.md
index 89607e0a..1f809141 100644
--- a/book/en-us/toc.md
+++ b/book/en-us/toc.md
@@ -1,9 +1,13 @@
-# C++ 11/14/17/20 On The Fly
+# C++11 to C++26 On The Fly
## Table of Contents
- [**Preface**](./00-preface.md)
-- [**Chapter 01 Towards Modern C++**](./01-intro.md)
+### Part I — Language Core
+
+*Enhancements to the language itself: how you declare and deduce types, and what happens at runtime.*
+
+- [**Chapter 01: Towards Modern C++**](./01-intro.md)
+ 1.1 Deprecated Features
+ 1.2 Compatibility with C
+ Further Readings
@@ -51,7 +55,11 @@
+ rvalue reference and lvalue reference
+ Move semantics
+ Perfect forwarding
-- [**Chapter 04 Containers**](./04-containers.md)
+### Part II — The Standard Library
+
+*Batteries included: containers, smart pointers, regular expressions, concurrency, and the filesystem.*
+
+- [**Chapter 04: Containers**](./04-containers.md)
+ 4.1 Linear containers
+ `std::array`
+ `std::forward_list`
@@ -62,12 +70,12 @@
+ basic operation
+ runtime indexing `std::variant`
+ merge and iteration
-- [**Chapter 05 Smart Pointers and Memory Management**](./05-pointers.md)
+- [**Chapter 05: Smart Pointers and Memory Management**](./05-pointers.md)
+ 5.1 RAII and reference counting
+ 5.2 `std::shared_ptr`
+ 5.3 `std::unique_ptr`
+ 5.4 `std::weak_ptr`
-- [**Chapter 06 Regular Expression**](./06-regex.md)
+- [**Chapter 06: Regular Expression**](./06-regex.md)
+ 6.1 Introduction
+ Ordinary characters
+ Special characters
@@ -76,7 +84,7 @@
+ `std::regex`
+ `std::regex_match`
+ `std::match_results`
-- [**Chapter 07 Parallelism and Concurrency**](./07-thread.md)
+- [**Chapter 07: Parallelism and Concurrency**](./07-thread.md)
+ 7.1 Basic of Parallelism
+ 7.2 Mutex and Critical Section
+ 7.3 Futures
@@ -85,10 +93,10 @@
+ Atomic Operation
+ Consistency Model
+ Memory Orders
-- [**Chapter 08 File System**](./08-filesystem.md)
+- [**Chapter 08: File System**](./08-filesystem.md)
+ 8.1 Documents and links
+ 8.2 `std::filesystem`
-- [**Chapter 09 Minor Features**](./09-others.md)
+- [**Chapter 09: Minor Features**](./09-others.md)
+ 9.1 New Types
+ `long long int`
+ 9.2 `noexcept` and Its Operations
@@ -96,17 +104,47 @@
+ Raw String Literal
+ Custom String Literal
+ 9.4 Memory Alignment
-- [**Chapter 10 Outlook: Introduction of C++20**](./10-cpp20.md)
+### Part III — By Standard Version
+
+*A tour of what each recent standard adds: C++20, C++23, and the forthcoming C++26.*
+
+- [**Chapter 10: C++20**](./10-cpp20.md)
+ 10.1 Concept
- + 10.2 Range
- + 10.3 Module
+ + 10.2 Module
+ + 10.3 Range
+ 10.4 Coroutine
- + 10.5 Transaction Memory
+- [**Chapter 11: C++23**](./11-cpp23.md)
+ + 11.1 Language features
+ - Deducing this (explicit object parameter)
+ - if consteval
+ - Multidimensional subscript operator
+ - auto(x) and static operator()
+ - [[assume]]
+ + 11.2 The standard library
+ - std::expected
+ - std::print and std::println
+ - std::mdspan
+ - std::flat_map and std::flat_set
+ - Ranges additions
+- [**Chapter 12: C++26 (Outlook)**](./12-cpp26.md)
+ + 12.1 Language features
+ - Static reflection
+ - Contracts
+ - Pack indexing
+ - = delete with a reason
+ - The placeholder _
+ + 12.2 The standard library
+ - std::execution
+ - Saturation arithmetic
+ - Other library facilities
+### Appendices
+
- [**Appendix 1: Further Study Materials**](./appendix1.md)
- [**Appendix 2: Modern C++ Best Practices**](./appendix2.md)
+- [**Appendix 3: Modern C++ Feature Index**](./appendix3.md)
Table of Content | Last Chapter | [Next Chapter: Preface](./00-preface.md)
## Licenses
-Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
\ No newline at end of file
+Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).
\ No newline at end of file
diff --git a/book/zh-cn/00-preface.md b/book/zh-cn/00-preface.md
index a60a0ef6..c8367695 100644
--- a/book/zh-cn/00-preface.md
+++ b/book/zh-cn/00-preface.md
@@ -11,9 +11,9 @@ order: 0
## 引言
C++ 是一个用户群体相当大的语言。从 C++98 的出现到 C++11 的正式定稿经历了长达十年多之久的积累。C++14/17 则是作为对 C++11 的重要补充和优化,C++20 则将这门语言领进了现代化的大门,所有这些新标准中扩充的特性,给 C++ 这门语言注入了新的活力。
-那些还在坚持使用**传统 C++**(本书把 C++98 及其之前的 C++ 特性均称之为传统 C++)而未接触过现代 C++ 的 C++ 程序员在见到诸如 Lambda 表达式这类全新特性时,甚至会流露出『学的不是同一门语言』的惊叹之情。
+那些还在坚持使用**传统 C++** (本书把 C++98 及其之前的 C++ 特性均称之为传统 C++)而未接触过现代 C++ 的 C++ 程序员在见到诸如 Lambda 表达式这类全新特性时,甚至会流露出『学的不是同一门语言』的惊叹之情。
-**现代 C++** (本书中均指 C++11/14/17/20) 为传统 C++ 注入的大量特性使得整个 C++ 变得更加像一门现代化的语言。现代 C++ 不仅仅增强了 C++ 语言自身的可用性,`auto` 关键字语义的修改使得我们更加有信心来操控极度复杂的模板类型。同时还对语言运行期进行了大量的强化,Lambda 表达式的出现让 C++ 具有了『匿名函数』的『闭包』特性,而这一特性几乎在现代的编程语言(诸如 Python/Swift/... )中已经司空见惯,右值引用的出现解决了 C++ 长期以来被人诟病的临时对象效率问题等等。
+**现代 C++** (本书中均指 C++11 至 C++26) 为传统 C++ 注入的大量特性使得整个 C++ 变得更加像一门现代化的语言。现代 C++ 不仅仅增强了 C++ 语言自身的可用性,`auto` 关键字语义的修改使得我们更加有信心来操控极度复杂的模板类型。同时还对语言运行期进行了大量的强化,Lambda 表达式的出现让 C++ 具有了『匿名函数』的『闭包』特性,而这一特性几乎在现代的编程语言(诸如 Python/Swift/... )中已经司空见惯,右值引用的出现解决了 C++ 长期以来被人诟病的临时对象效率问题等等。
C++17 则是近三年依赖 C++ 社区一致推进的方向,也指出了 **现代C++** 编程的一个重要发展方向。尽管它的出现并不如 C++11 的分量之重,但它包含了大量小而美的语言与特性(例如结构化绑定),这些特性的出现再一次修正了我们在 C++ 中的编程范式。
@@ -26,26 +26,38 @@ C++17 则是近三年依赖 C++ 社区一致推进的方向,也指出了 **现
1. 本书假定读者已经熟悉了传统 C++ ,至少在阅读传统 C++ 代码上不具备任何困难。换句话说,那些长期使用传统 C++ 进行编码的人、渴望在短时间内迅速了解**现代 C++** 特性的人非常适合阅读本书;
2. 本书一定程度上介绍了一些现代 C++ 的**黑魔法**,但这些魔法毕竟有限,不适合希望进阶学习现代 C++ 的读者,本书的定位系**现代 C++ 的快速上手**。当然,希望进阶学习的读者可以使用本书来回顾并检验自己对 **现代 C++** 的熟悉度。
+## 如何阅读本书
+
+现代 C++ 的内容十分庞大,你并不需要从头到尾逐页阅读。本书假定你已经熟悉 C++ 的基础——大致相当于 C++11 时期的语言:类、模板,以及常用的标准库容器等。如果你正属于这种情况(比如在大学里学过一些 C++),完全可以跳过你已经熟悉的部分,把精力集中在对你而言全新的内容上。
+
+为方便你查阅,本书内置了几处导航辅助:
+
+- 每个特性都标注了引入它的标准版本(例如 *(C++17 引入)*),让你一眼就能看出哪些内容比你已知的 C++ 更新。
+- [附录 3](./appendix3.md) 是一份特性索引,将每个特性映射到它所在的章节以及引入它的标准版本——便于你直接跳到「C++20 新增了什么」,或日后查阅某个特性。
+- 全书分为三个部分:**语言核心**(第 1–3 章)、**标准库**(第 4–9 章),以及**按标准版本概览**(第 10–12 章,分别介绍 C++20、C++23 与即将到来的 C++26)。
+
+与其试图记住每一个特性,不如把本书当作一本可以随时回查的参考书:当真实的问题需要某个特性时,再去学习它。
+
## 本书目的
-本书号称『高速上手』,从内容上对二十一世纪二十年代之前产生 C++ 的相关特性做了非常相对全面的介绍,读者可以自行根据下面的目录选取感兴趣的内容进行学习,快速熟悉需要了解的内容。这些特性并不需要全部掌握,只需针对自己的使用需求和特定的应用场景,学习、查阅最适合自己的新特性即可。
+本书号称『高速上手』,从内容上对从 C++11 一直到 C++26 的相关特性做了相对全面的介绍,读者可以自行根据下面的目录选取感兴趣的内容进行学习,快速熟悉需要了解的内容。这些特性并不需要全部掌握,只需针对自己的使用需求和特定的应用场景,学习、查阅最适合自己的新特性即可。
同时,本书在介绍这些特性的过程中,尽可能简单明了的介绍了这些特性产生的历史背景和技术需求,这为理解这些特性、运用这些特性提供了很大的帮助。
-此外,笔者希望读者在阅读本书后,能够努力在新项目中直接使用 C++17,并努力将旧项目逐步迁移到 C++17。也算是笔者为推进现代 C++ 的普及贡献了一些绵薄之力。
+此外,笔者希望读者在阅读本书后,能够努力在新项目中直接使用现代 C++,并努力将旧项目逐步迁移到现代 C++。也算是笔者为推进现代 C++ 的普及贡献了一些绵薄之力。
## 相关代码
-本书每章中都出现了大量的代码,如果你在跟随本书介绍特性的思路编写自己的代码遇到问题时,不妨读一读随书附上的源码,你可以在[这里](../../code)中找到书中介绍过的全部的源码,所有代码按章节组织,文件夹名称为章节序号。
+本书每章中都出现了大量的代码,如果你在跟随本书介绍特性的思路编写自己的代码遇到问题时,不妨读一读随书附上的源码,你可以在[这里](https://github.com/changkun/modern-cpp-tutorial/tree/master/code)中找到书中介绍过的全部的源码,所有代码按章节组织,文件夹名称为章节序号。
## 随书习题
-本书每章最后还加入了少量难度极小的习题,仅用于检验你是否能混合运用当前章节中的知识点。你可以在[这里](../../exercises)找到习题的答案,文件夹名称为章节序号。
+本书每章最后还加入了少量难度极小的习题,仅用于检验你是否能混合运用当前章节中的知识点。你可以在[这里](https://github.com/changkun/modern-cpp-tutorial/tree/master/exercises)找到习题的答案,文件夹名称为章节序号。
[返回目录](./toc.md) | [下一章 迈向现代 C++](./01-intro.md)
## 许可
-知识共享许可协议
+知识共享许可协议
-本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。
+本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。
diff --git a/book/zh-cn/01-intro.md b/book/zh-cn/01-intro.md
index 80e324c1..a74a3724 100644
--- a/book/zh-cn/01-intro.md
+++ b/book/zh-cn/01-intro.md
@@ -138,12 +138,12 @@ clean:
## 进一步阅读的参考文献
- [C++ 语言导学. Bjarne Stroustrup](https://www.amazon.cn/dp/B00WUBYBYS/ref=sr_1_1?ie=UTF8&qid=1522400738&sr=8-1&keywords=C%2B%2B+%E8%AF%AD%E8%A8%80%E5%AF%BC%E5%AD%A6)
-- [C++ 历史](http://en.cppreference.com/w/cpp/language/history)
-- [C++ 特性在 GCC/Clang 等编译器中的支持情况](http://en.cppreference.com/w/cpp/compiler_support)
+- [C++ 历史](https://en.cppreference.com/w/cpp/language/history)
+- [C++ 特性在 GCC/Clang 等编译器中的支持情况](https://en.cppreference.com/w/cpp/compiler_support)
- [C++98 与 C99 之间的区别](http://david.tribble.com/text/cdiffs.htm#C99-vs-CPP98)
## 许可
-知识共享许可协议
+知识共享许可协议
-本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。
+本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。
diff --git a/book/zh-cn/02-usability.md b/book/zh-cn/02-usability.md
index 7e1fe026..50452405 100644
--- a/book/zh-cn/02-usability.md
+++ b/book/zh-cn/02-usability.md
@@ -14,9 +14,11 @@ order: 2
### nullptr
-`nullptr` 出现的目的是为了替代 `NULL`。在某种意义上来说,传统 C++ 会把 `NULL`、`0` 视为同一种东西,这取决于编译器如何定义 `NULL`,有些编译器会将 `NULL` 定义为 `((void*)0)`,有些则会直接将其定义为 `0`。
+*(C++11)*
-C++ **不允许**直接将 `void *` 隐式转换到其他类型。但如果编译器尝试把 `NULL` 定义为 `((void*)0)`,那么在下面这句代码中:
+`nullptr` 出现的目的是为了替代 `NULL`。 C 与 C++ 语言中有**空指针常量**,它们能被隐式转换成任何指针类型的空指针值,或 C++ 中的任何成员指针类型的空成员指针值。 `NULL` 由标准库实现提供,并被定义为实现定义的空指针常量。在 C 中,有些标准库会把 `NULL` 定义为 `((void*)0)` 而有些将它定义为 `0`。
+
+C++ **不允许**直接将 `void *` 隐式转换到其他类型,从而 `((void*)0)` 不是 `NULL` 的合法实现。如果标准库尝试把 `NULL` 定义为 `((void*)0)`,那么下面这句代码中会出现编译错误:
```cpp
char *ch = NULL;
@@ -29,7 +31,7 @@ void foo(char*);
void foo(int);
```
-那么 `foo(NULL);` 这个语句将会去调用 `foo(int)`,从而导致代码违反直觉。
+那么 `foo(NULL);` 这个语句的行为将取决于 `NULL` 的具体实现:当 `NULL` 被定义为 `0` 时(例如 MSVC),它会去调用 `foo(int)`,从而导致代码违反直觉;而当 `NULL` 被定义为 GCC/Clang 的内建常量 `__null` 时,`foo(NULL)` 则会因为 `char*` 与 `int` 两个重载都能匹配而产生二义性,导致编译失败。无论哪种情况,`NULL` 在重载决议中的表现都不符合空指针应有的语义。
为了解决这个问题,C++11 引入了 `nullptr` 关键字,专门用来区分空指针、`0`。而 `nullptr` 的类型为 `nullptr_t`,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。
@@ -77,6 +79,8 @@ foo(char*) is called
### constexpr
+*(C++11 引入,C++14 起放宽)*
+
C++ 本身已经具备了常量表达式的概念,比如 `1+2`, `3*4` 这种表达式总是会产生相同的结果并且没有任何副作用。如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。一个非常明显的例子就是在数组的定义阶段:
```cpp
@@ -119,7 +123,7 @@ int main() {
上面的例子中,`char arr_4[len_2]` 可能比较令人困惑,因为 `len_2` 已经被定义为了常量。为什么 `char arr_4[len_2]` 仍然是非法的呢?这是因为 C++ 标准中数组的长度必须是一个常量表达式,而对于 `len_2` 而言,这是一个 `const` 常数,而不是一个常量表达式,因此(即便这种行为在大部分编译器中都支持,但是)它是一个非法的行为,我们需要使用接下来即将介绍的 C++11 引入的 `constexpr` 特性来解决这个问题;而对于 `arr_5` 来说,C++98 之前的编译器无法得知 `len_foo()` 在运行期实际上是返回一个常数,这也就导致了非法的产生。
-> 注意,现在大部分编译器其实都带有自身编译优化,很多非法行为在编译器优化的加持下会变得合法,若需重现编译报错的现象需要使用老版本的编译器。
+> 注意,一些编译器(如 GCC、Clang)默认开启了编译器扩展,支持了 C 语言的特性:"[变长数组](https://zh.cppreference.com/w/c/language/array#.E9.9D.9E.E5.B8.B8.E9.87.8F.E9.95.BF.E5.BA.A6.E6.95.B0.E7.BB.84)",允许定义数组时,其长度的表达式可以是非常量表达式。导致以上注释了非法的代码可以通过编译。想要禁用扩展可以添加编译选项 [`-pedantic-errors`](https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-pedantic-errors)(GCC 与 Clang 都可用)。
C++11 提供了 `constexpr` 让用户显式的声明函数或对象构造函数在编译期会成为常量表达式,这个关键字明确的告诉编译器应该去验证 `len_foo` 在编译期就应该是一个常量表达式。
@@ -153,6 +157,8 @@ constexpr int fibonacci(const int n) {
### if/switch 变量声明强化
+*(C++17)*
+
在传统 C++ 中,变量的声明虽然能够位于任何位置,甚至于 `for` 语句内能够声明一个临时变量 `int`,但始终没有办法在 `if` 和 `switch` 语句中声明一个临时的变量。例如:
```cpp
@@ -163,7 +169,7 @@ constexpr int fibonacci(const int n) {
int main() {
std::vector vec = {1, 2, 3, 4};
- // 在 c++17 之前
+ // 在 C++17 之前,能用 `auto` 简化
const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 2);
if (itr != vec.end()) {
*itr = 3;
@@ -175,13 +181,14 @@ int main() {
*itr2 = 4;
}
- // 将输出 1, 4, 3, 4
- for (std::vector::iterator element = vec.begin(); element != vec.end(); ++element)
+ // 将输出 1, 4, 3, 4;能用 `auto` 简化
+ for (std::vector::iterator element = vec.begin(); element != vec.end();
+ ++element)
std::cout << *element << std::endl; } ``` -在上面的代码中,我们可以看到 `itr` 这一变量是定义在整个 `main()` 的作用域内的,这导致当我们需要再次遍历整个 `std::vectors` 时,需要重新命名另一个变量。C++17 消除了这一限制,使得我们可以在 `if`(或 `switch`)中完成这一操作: +在上面的代码中,我们可以看到 `itr` 这一变量是定义在整个 `main()` 的作用域内的,这导致当我们需要再次遍历整个 `std::vector` 时,需要重新命名另一个变量。C++17 消除了这一限制,使得我们可以在 `if`(或 `switch`)中完成这一操作: ```cpp // 将临时变量放到 if 语句内 @@ -195,6 +202,8 @@ if (const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 3);
### 初始化列表
+*(C++11)*
+
初始化是一个非常重要的语言特性,最常见的就是在对象进行初始化时进行使用。
在传统 C++ 中,不同的对象有着不同的初始化方法,例如普通数组、
POD (**P**lain **O**ld **D**ata,即没有构造、析构和虚函数的类或结构体)
@@ -228,11 +237,13 @@ int main() {
}
```
-为了解决这个问题,C++11 首先把初始化列表的概念绑定到了类型上,并将其称之为 `std::initializer_list`,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁,例如:
+为解决这个问题,C++11 首先把初始化列表的概念绑定到类型上,称其为 `std::initializer_list`,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁,例如:
```cpp
#include
#include
+#include
+
class MagicFoo {
public:
std::vector vec;
@@ -247,7 +258,9 @@ int main() {
MagicFoo magicFoo = {1, 2, 3, 4, 5};
std::cout << "magicFoo: "; - for (std::vector::iterator it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) std::cout << *it << std::endl; + for (std::vector::iterator it = magicFoo.vec.begin();
+ it != magicFoo.vec.end(); ++it)
+ std::cout << *it << std::endl; } ``` @@ -258,7 +271,8 @@ int main() { ```Cpp public: void foo(std::initializer_list list) {
- for (std::initializer_list::iterator it = list.begin(); it != list.end(); ++it) vec.push_back(*it);
+ for (std::initializer_list::iterator it = list.begin();
+ it != list.end(); ++it) vec.push_back(*it);
}
magicFoo.foo({6,7,8,9});
@@ -272,9 +286,11 @@ Foo foo2 {3, 4};
### 结构化绑定
-结构化绑定提供了类似其他语言中提供的多返回值的功能。在容器一章中,我们会学到 C++11 新增了 `std::tuple` 容器用于构造一个元组,进而囊括多个返回值。但缺陷是,C++11/14 并没有提供一种简单的方法直接从元组中拿到并定义元组中的元素,尽管我们可以使用 `std::tie` 对元组进行拆包,但我们依然必须非常清楚这个元组包含多少个对象,各个对象是什么类型,非常麻烦。
+*(C++17 引入)*
+
+函数常常需要「一次返回多个值」——例如同时返回计算结果与一个表示成败的状态。在传统 C++ 中,这件事并不优雅:我们要么为此专门定义一个结构体,要么用 `std::tuple` 把多个值打包返回,可一旦要把它们取出来就变得相当笨拙——用 `std::tie` 拆包时,必须事先声明好每一个变量,还要准确知道元组里有几个元素、各自是什么类型,稍有出入便会出错。
-C++17 完善了这一设定,给出的结构化绑定可以让我们写出这样的代码:
+C++17 的**结构化绑定 (structured bindings)** 正是为消除这种笨拙而生:它允许我们用一行代码,把一个元组、`std::pair`、原生数组或带有公开数据成员的结构体「拆开」,并直接绑定到一组有名字的变量上,类型则交由编译器推导:
```cpp
#include
@@ -291,6 +307,19 @@ int main() {
}
```
+与 `std::tie` 相比,结构化绑定既不需要事先声明变量、也不需要手写类型,而且不仅适用于元组,对原生数组和聚合结构体同样有效。这一点在遍历关联容器时尤为实用:我们可以把键值对直接绑定为有意义的名字,而不必再写 `it->first` / `it->second`:
+
+```cpp
+#include
+#include
+#include
+
+std::map scores{{"alice", 90}, {"bob", 80}};
+for (const auto& [name, score] : scores) {
+ std::cout << name << ": " << score << '\n'; +} +``` + 关于 `auto` 类型推导会在 [auto 类型推导](#auto)一节中进行介绍。 ## 2.3 类型推导 @@ -301,6 +330,8 @@ C++11 引入了 `auto` 和 `decltype` 这两个关键字实现了类型推导, ### auto +*(C++11)* + `auto` 在很早以前就已经进入了 C++,但是他始终作为一个存储类型的指示符存在,与 `register` 并存。在传统 C++ 中,如果一个变量没有声明为 `register` 变量,将自动被视为一个 `auto` 变量。而随着 `register` 被弃用(在 C++17 中作为保留关键字,以后使用,目前不具备实际意义),对 `auto` 的语义变更也就非常自然了。 使用 `auto` 进行类型推导的一个最为常见而且显著的例子就是迭代器。你应该在前面的小节里看到了传统 C++ 中冗长的迭代写法: @@ -308,8 +339,8 @@ C++11 引入了 `auto` 和 `decltype` 这两个关键字实现了类型推导, ```cpp // 在 C++11 之前 // 由于 cbegin() 将返回 vector::const_iterator
-// 所以 itr 也应该是 vector::const_iterator 类型
-for(vector::const_iterator it = vec.cbegin(); itr != vec.cend(); ++it)
+// 所以 it 也应该是 vector::const_iterator 类型
+for(vector::const_iterator it = vec.cbegin(); it != vec.cend(); ++it)
```
而有了 `auto` 之后可以:
@@ -348,20 +379,29 @@ auto i = 5; // i 被推导为 int
auto arr = new auto(10); // arr 被推导为 int *
```
-> **注意**:`auto` 不能用于函数传参,因此下面的做法是无法通过编译的(考虑重载的问题,我们应该使用模板):
->
-> ```cpp
-> int add(auto x, auto y);
->
-> 2.6.auto.cpp:16:9: error: 'auto' not allowed in function prototype
-> int add(auto x, auto y) {
-> ^~~~
-> ```
+从 C++ 14 起,`auto` 能用于 lambda 表达式中的函数传参,而 C++ 20 起该功能推广到了一般的函数。考虑下面的例子:
+
+
+```cpp
+auto add14 = [](auto x, auto y) -> int {
+ return x+y;
+}
+
+int add20(auto x, auto y) {
+ return x+y;
+}
+
+auto i = 5; // type int
+auto j = 6; // type int
+std::cout << add14(i, j) << std::endl; +std::cout << add20(i, j) << std::endl; +``` +>
-> 此外,`auto` 还不能用于推导数组类型:
+> **注意**:`auto` 还不能用于推导数组类型:
>
> ```cpp
-> auto auto_arr2[10] = {arr}; // 错误, 无法推导数组元素类型
+> auto auto_arr2[10] = {arr}; // 错误, 无法推导数组元素类型
>
> 2.6.auto.cpp:30:19: error: 'auto_arr2' declared as array of 'auto'
> auto auto_arr2[10] = {arr};
@@ -369,7 +409,9 @@ auto arr = new auto(10); // arr 被推导为 int *
### decltype
-`decltype` 关键字是为了解决 `auto` 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 `typeof` 很相似:
+*(C++11)*
+
+`decltype` 关键字是为了解决 `auto` 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 `typeof` 很相似(`typeof` 是 GCC、Clang 等编译器长期提供的非标准扩展,虽然最终在 C23 中被标准化,但从未成为 C++ 的标准特性):
```cpp
decltype(表达式)
@@ -403,19 +445,21 @@ type z == type x
### 尾返回类型推导
-你可能会思考,在介绍 `auto` 时,我们已经提过 `auto` 不能用于函数形参进行类型推导,那么 `auto` 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写:
+*(C++11)*
+
+你可能会思考, `auto` 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写:
```cpp
template
R add(T x, U y) {
- return x+y
+ return x+y;
}
```
-> 注意:typename 和 class 在模板参数列表中没有区别,在 typename 这个关键字出现之前,都是使用 class 来定义模板参数的。但在模板中定义有[嵌套依赖类型](http://en.cppreference.com/w/cpp/language/dependent_name#The_typename_disambiguator_for_dependent_names)的变量时,需要用 typename 消除歧义
+> 注意:typename 和 class 在模板参数列表中没有区别,在 typename 这个关键字出现之前,都是使用 class 来定义模板参数的。但在模板中定义有[嵌套依赖类型](https://en.cppreference.com/w/cpp/language/dependent_name#The_typename_disambiguator_for_dependent_names)的变量时,需要用 typename 消除歧义
-这样的代码其实变得很丑陋,因为程序员在使用这个模板函数的时候,必须明确指出返回类型。但事实上我们并不知道 `add()` 这个函数会做什么样的操作,获得一个什么样的返回类型。
+这样的代码其实变得很丑陋,因为程序员在使用这个模板函数的时候,必须明确指出返回类型。但事实上我们并不知道 `add()` 这个函数会做什么样的操作,以及获得一个什么样的返回类型。
在 C++11 中这个问题得到解决。虽然你可能马上会反应出来使用 `decltype` 推导 `x+y` 的类型,写出这样的代码:
@@ -458,6 +502,8 @@ std::cout << "q: " << q << std::endl; ### decltype(auto) +*(C++14)* + `decltype(auto)` 是 C++14 开始提供的一个略微复杂的用法。> 要理解它你需要知道 C++ 中参数转发的概念,我们会在[语言运行时强化](./03-runtime.md)一章中详细介绍,你可以到时再回来看这一小节的内容。
@@ -496,6 +542,8 @@ decltype(auto) look_up_a_string_2() {
### if constexpr
+*(C++17)*
+
正如本章开头出,我们知道了 C++11 引入了 `constexpr` 关键字,它将表达式或函数编译为常量结果。一个很自然的想法是,如果我们把这一特性引入到条件判断中去,让代码在编译时就完成分支判断,岂不是能让程序效率更高?C++17 将 `constexpr` 这个关键字引入到 `if` 语句中,允许在代码中声明常量表达式的判断条件,考虑下面的代码:
```cpp
@@ -532,6 +580,8 @@ int main() {
### 区间 for 迭代
+*(C++11)*
+
终于,C++11 引入了基于范围的迭代写法,我们拥有了能够写出像 Python 一样简洁的循环语句,我们可以进一步简化前面的例子:
```cpp
@@ -552,12 +602,36 @@ int main() {
}
```
+基于范围的 for 循环本质上只是语法糖,编译器会把
+
+```cpp
+for (range_declaration : range_expression) loop_statement
+```
+
+大致展开为(C++17 起):
+
+```cpp
+{
+ auto && __range = range_expression;
+ auto __begin = begin_expr; // 数组为 __range,类类型为 __range.begin() 或 begin(__range)
+ auto __end = end_expr; // 数组为 __range + N,类类型为 __range.end() 或 end(__range)
+ for (; __begin != __end; ++__begin) {
+ range_declaration = *__begin;
+ loop_statement
+ }
+}
+```
+
+因此,只要一个类型提供了可用的 `begin()` 与 `end()`(成员函数,或可通过 ADL 找到的自由函数),并且返回的迭代器支持 `!=`、解引用 `*` 与前置 `++`,它就能被基于范围的 for 循环遍历——这也正是让自定义容器支持区间 for 的方法。
+
## 2.5 模板
C++ 的模板一直是这门语言的一种特殊的艺术,模板甚至可以独立作为一门新的语言来进行使用。模板的哲学在于将一切能够在编译期处理的问题丢到编译期进行处理,仅在运行时处理那些最核心的动态服务,进而大幅优化运行期的性能。因此模板也被很多人视作 C++ 的黑魔法之一。
### 外部模板
+*(C++11)*
+
传统 C++ 中,模板只有在使用时才会被编译器实例化。换句话说,只要在每个编译单元(文件)中编译的代码中遇到了被完整定义的模板,都会实例化。这就产生了重复实例化而导致的编译时间的增加。并且,我们没有办法通知编译器不要触发模板的实例化。
为此,C++11 引入了外部模板,扩充了原来的强制编译器在特定位置实例化模板的语法,使我们能够显式的通知编译器何时进行模板的实例化:
@@ -569,6 +643,8 @@ extern template class std::vector; // 不在该当前编译文件中实
### 尖括号 ">"
+*(C++11)*
+
在传统 C++ 的编译器中,`>>`一律被当做右移运算符来进行处理。但实际上我们很容易就写出了嵌套模板的代码:
```cpp
@@ -589,6 +665,8 @@ std::vector2)>> magic; // 合法, 但不建议写出这样的代
### 类型别名模板
+*(C++11)*
+
在了解类型别名模板之前,需要理解『模板』和『类型』之间的不同。仔细体会这句话:**模板是用来产生类型的。**在传统 C++ 中,`typedef` 可以为类型定义一个新的名称,但是却没有办法为模板定义一个新的名称。因为,模板不是类型。例如:
```cpp
@@ -621,6 +699,8 @@ int main() {
### 变长参数模板
+*(C++11)*
+
模板一直是 C++ 所独有的**黑魔法**(一起念:**Dark Magic**)之一。
在 C++11 之前,无论是类模板还是函数模板,都只能按其指定的样子,
接受一组固定数量的模板参数;而 C++11 加入了新的表示方法,
@@ -738,6 +818,8 @@ auto printf3(T value, Ts... args) {
### 折叠表达式
+*(C++17)*
+
C++ 17 中将变长参数这种特性进一步带给了表达式,考虑下面这个例子:
```cpp
@@ -753,6 +835,8 @@ int main() {
### 非类型模板参数推导
+*(C++17)*
+
前面我们主要提及的是模板参数的一种形式:类型模板参数。
```cpp
@@ -773,7 +857,7 @@ public:
void free(T& item);
private:
T data[BufSize];
-}
+};
buffer_t buf; // 100 作为模板参数
```
@@ -796,11 +880,47 @@ int main() {
}
```
+### SFINAE 与 `std::enable_if`
+
+*(C++11)*
+
+SFINAE 是 "Substitution Failure Is Not An Error"(替换失败并非错误)的缩写。它描述的是这样一条规则:在对模板参数进行替换时,如果在**直接上下文 (immediate context)** 中产生了非法的类型或表达式,编译器并不会报错,而是悄悄地把这个候选从重载集合中剔除。这正是在 C++20 的 concept 出现之前,对模板参数进行约束的主要手段。
+
+最常见的工具是 `` 中的 `std::enable_if`。下面的 `describe` 只对整型可见:
+
+```cpp
+#include
+
+template >>
+void describe(T) {
+ std::cout << "integral" << std::endl; +} + +describe(42); // 正确 +// describe(3.14); // 编译错误:浮点类型不满足约束,该重载被剔除 +``` + +另一种常见形式是**表达式 SFINAE**,它借助 `decltype` 来探测某个表达式是否合法,从而在编译期检测类型是否具备某种能力: + +```cpp +// 当且仅当 c.size() 是合法表达式时,这个重载才会参与候选 +template
+auto has_size(const T& c) -> decltype(c.size(), std::true_type{}) {
+ return std::true_type{};
+}
+std::false_type has_size(...) { return std::false_type{}; }
+```
+
+SFINAE 虽然强大,但写法晦涩、错误信息冗长。C++20 的 [概念 (concept)](./10-cpp20.md#概念与约束) 正是为了以更直观、可读的方式表达这类约束而引入的,可以将其视为 SFINAE 的现代替代品。
+
## 2.6 面向对象
### 委托构造
-C++11 引入了委托构造的概念,这使得构造函数可以在同一个类中一个构造函数调用另一个构造函数,从而达到简化代码的目的:
+*(C++11)*
+
+一个类往往有多个构造函数,它们之间常常包含大量重复的初始化逻辑。在 C++11 之前,要复用这些逻辑只能把代码复制进每个构造函数,或抽取一个私有的初始化函数——但后者无法用来初始化 `const` 成员和引用成员。C++11 引入的**委托构造**允许一个构造函数把初始化工作「委托」给同一个类中的另一个构造函数,从而消除这种重复:
```cpp
#include
@@ -825,7 +945,9 @@ int main() {
### 继承构造
-在传统 C++ 中,构造函数如果需要继承是需要将参数一一传递的,这将导致效率低下。C++11 利用关键字 `using` 引入了继承构造函数的概念:
+*(C++11)*
+
+在传统 C++ 中,如果派生类想要复用基类的构造函数,必须在派生类里逐一重新声明、并把参数一一转发给基类,十分繁琐。C++11 利用关键字 `using` 引入了继承构造函数的概念,让派生类可以「一键」继承基类的全部构造函数:
```cpp
#include
@@ -853,6 +975,8 @@ int main() {
### 显式虚函数重载
+*(C++11)*
+
在传统 C++ 中,经常容易发生意外重载虚函数的事情。例如:
```cpp
@@ -870,7 +994,7 @@ C++11 引入了 `override` 和 `final` 这两个关键字来防止上述情形
#### override
-当重载虚函数时,引入 `override` 关键字将显式的告知编译器进行重载,编译器将检查基函数是否存在这样的虚函数,否则将无法通过编译:
+当重载虚函数时,引入 `override` 关键字将显式的告知编译器进行重载,编译器将检查基函数是否存在这样的其函数签名一致的虚函数,否则将无法通过编译:
```cpp
struct Base {
@@ -903,6 +1027,8 @@ struct SubClass3: Base {
### 显式禁用默认函数
+*(C++11)*
+
在传统 C++ 中,如果程序员没有提供,编译器会默认为对象生成默认构造函数、
复制构造、赋值算符以及析构函数。
另外,C++ 也为所有类定义了诸如 `new` `delete` 这样的运算符。
@@ -930,6 +1056,8 @@ class Magic {
### 强类型枚举
+*(C++11)*
+
在传统 C++中,枚举类型并非类型安全,枚举类型会被视作整数,则会让两种完全不同的枚举类型可以进行直接的比较(虽然编译器给出了检查,但并非所有),**甚至同一个命名空间中的不同枚举类型的枚举值名字不能相同**,这通常不是我们希望看到的结果。
C++11 引入了枚举类(enumeration class),并使用 `enum class` 的语法进行声明:
@@ -948,7 +1076,7 @@ enum class new_enum : unsigned int {
```cpp
if (new_enum::value3 == new_enum::value4) {
- // 会输出
+ // 会输出true
std::cout << "new_enum::value3 == new_enum::value4" << std::endl; } ``` @@ -960,7 +1088,9 @@ if (new_enum::value3 == new_enum::value4) { ```cpp #include
template
-std::ostream& operator<<(typename std::enable_if::value, std::ostream>::type& stream, const T& e)
+std::ostream& operator<<( + typename std::enable_if::value,
+ std::ostream>::type& stream, const T& e)
{
return stream << static_cast::type>(e);
}
@@ -972,6 +1102,117 @@ std::ostream& operator<<(typename std::enable_if::value, std::os
std::cout << new_enum::value3 << std::endl ``` +## 2.7 其他语言特性 + +### 内联变量 + +*(C++17)* + +在 C++17 之前,类的非常量静态成员变量必须在类外单独定义,而在头文件中定义全局变量则会因为被多个翻译单元包含而导致重复定义的链接错误。C++17 引入了 `inline` 变量,允许在头文件中定义变量(包括类的静态成员),即便被多个翻译单元包含也不会违反单一定义规则 (ODR): + +```cpp +struct Widget { + static inline int count = 0; // C++17:在类内定义并初始化静态成员 +}; +inline int global_value = 42; // 可安全地放在头文件中 +``` + +这极大地简化了「仅有头文件」的库的编写。 + +### 嵌套命名空间 + +*(C++17)* + +C++17 允许使用 `::` 一次性书写嵌套命名空间的定义,不再需要层层缩进: + +```cpp +// C++17 之前 +namespace A { + namespace B { + namespace C { + int value; + } + } +} + +// C++17 起 +namespace A::B::C { + int value; +} +``` + +### constexpr lambda + +*(C++17)* + +从 C++17 开始,满足常量表达式要求的 Lambda 表达式会隐式地成为 `constexpr`(也可以显式标注 `constexpr`),从而能够在编译期求值: + +```cpp +constexpr auto add = [](int a, int b) { return a + b; }; +static_assert(add(1, 2) == 3, ""); + +constexpr int result = add(3, 4); // 在编译期求值,result == 7 +``` + +### 单参数 static_assert + +*(C++17)* + +`static_assert` 用于在编译期进行断言。在 C++17 之前,它必须提供一条诊断消息作为第二个参数;C++17 起,该消息成为可选项: + +```cpp +static_assert(sizeof(int)>= 2); // C++17:消息可省略
+static_assert(sizeof(int)>= 2, "int 至少需要 2 字节"); // 仍然可以提供消息
+```
+
+### 新的聚合规则
+
+*(C++17)*
+
+C++17 放宽了聚合 (aggregate) 的定义:聚合类型现在可以拥有公有基类(基类本身也必须是聚合),并且可以用花括号一并初始化基类子对象:
+
+```cpp
+struct Base { int a; };
+struct Derived : Base { int b; };
+
+Derived d{{1}, 2}; // {a}, b —— C++17 起合法
+```
+
+### 布尔逻辑元函数
+
+*(C++17)*
+
+C++17 在 `` 中引入了 `std::conjunction`、`std::disjunction`、`std::negation`,用于在编译期对其他类型特征进行逻辑与、或、非的组合(并且 `conjunction`/`disjunction` 具有短路特性):
+
+```cpp
+#include
+
+template
+constexpr bool is_signed_integral =
+ std::conjunction_v, std::is_signed>;
+
+static_assert(is_signed_integral);
+static_assert(!is_signed_integral);
+static_assert(std::negation_v>);
+```
+
+### `__has_include`
+
+*(C++17)*
+
+C++17 标准化了预处理器运算符 `__has_include`,用于在编译期检查某个头文件是否可用,从而写出可移植的条件包含逻辑:
+
+```cpp
+#if __has_include()
+# include
+# define HAS_OPTIONAL 1
+#else
+# define HAS_OPTIONAL 0
+#endif
+```
+
+这在需要兼容不同标准库版本,或在某个特性可能缺失时提供回退实现的场景中非常有用。
+
## 总结
本节介绍了现代 C++ 中对语言可用性的增强,其中笔者认为最为重要的几个特性是几乎所有人都需要了解并熟练使用的:
@@ -1012,6 +1253,6 @@ std::cout << new_enum::value3 << std::endl ## 许可 -知识共享许可协议
+知识共享许可协议
-本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。
+本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。
diff --git a/book/zh-cn/03-runtime.md b/book/zh-cn/03-runtime.md
index 87612c17..6a17ed27 100644
--- a/book/zh-cn/03-runtime.md
+++ b/book/zh-cn/03-runtime.md
@@ -16,6 +16,8 @@ Lambda 表达式是现代 C++ 中最重要的特性之一,而 Lambda 表达式
### 基础
+*(C++11)*
+
Lambda 表达式的基本语法如下:
```
@@ -67,19 +69,19 @@ void lambda_reference_capture() {
}
```
-**3. 隐式捕获**
+#### 3. 隐式捕获
-手动书写捕获列表有时候是非常复杂的,这种机械性的工作可以交给编译器来处理,这时候可以在捕获列表中写一个
+手动书写捕获列表有时候是非常复杂的,这种机械性的工作可以交给编译器来处理,这时候可以在捕获列表中写一个
`&` 或 `=` 向编译器声明采用引用捕获或者值捕获.
总结一下,捕获提供了 Lambda 表达式对外部值进行使用的功能,捕获列表的最常用的四种形式可以是:
- \[\] 空捕获列表
- \[name1, name2, ...\] 捕获一系列变量
-- \[&\] 引用捕获, 让编译器自行推导引用列表
-- \[=\] 值捕获, 让编译器自行推导值捕获列表
+- \[&\] 引用捕获, 从函数体内的使用确定引用捕获列表
+- \[=\] 值捕获, 从函数体内的使用确定值捕获列表
-**4. 表达式捕获**
+#### 4. 表达式捕获
> 这部分内容需要了解后面马上要提到的右值引用以及智能指针
@@ -90,15 +92,15 @@ C++14 给与了我们方便,允许捕获的成员用任意的表达式进行
```cpp
#include
-#include
+#include // std::make_unique
+#include // std::move
-int main() {
+void lambda_expression_capture() {
auto important = std::make_unique(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; - return 0; } ``` @@ -106,12 +108,12 @@ int main() { ### 泛型 Lambda -上一节中我们提到了 `auto` 关键字不能够用在参数表里,这是因为这样的写法会与模板的功能产生冲突。 -但是 Lambda 表达式并不是普通函数,所以 Lambda 表达式并不能够模板化。 -这就为我们造成了一定程度上的麻烦:参数表不能够泛化,必须明确参数表类型。 +*(C++14)* -幸运的是,这种麻烦只存在于 C++11 中,从 C++14 开始, -Lambda 函数的形式参数可以使用 `auto` 关键字来产生意义上的泛型: +上一节中我们提到了 `auto` 关键字不能够用在参数表里,这是因为这样的写法会与模板的功能产生冲突。 +但是 Lambda 表达式并不是普通函数,所以在没有明确指明参数表类型的情况下,Lambda 表达式并不能够模板化。 +幸运的是,这种麻烦只存在于 C++11 中,从 C++14 开始,Lambda 函数的形式参数可以使用 `auto` +关键字来产生意义上的泛型: ```cpp auto add = [](auto x, auto y) { @@ -129,6 +131,10 @@ add(1.1, 2.2); ### `std::function` +*(C++11)* + +在 C++ 中,「可以被调用的东西」其实形态各异——普通函数、函数指针、Lambda 表达式,以及任何重载了 `operator()` 的对象,它们的类型彼此不同,很难用一种统一的方式来保存和传递。`std::function` 正是为解决这一问题而生:它是一个类型安全的「可调用对象容器」,能够统一地存储、复制并调用任意可调用目标,从而让我们能像处理普通对象那样去处理「函数」。 + Lambda 表达式的本质是一个和函数对象类型相似的类类型(称为闭包类型)的对象(称为闭包对象), 当 Lambda 表达式的捕获列表为空时,闭包对象还能够转换为函数指针值进行传递,例如: @@ -136,7 +142,7 @@ Lambda 表达式的本质是一个和函数对象类型相似的类类型(称 #include
using foo = void(int); // 定义函数类型, using 的使用见上一节中的别名语法
-void functional(foo f) { // 定义在参数列表中的函数类型 foo 被视为退化后的函数指针类型 foo*
+void functional(foo f) { // 参数列表中定义的函数类型 foo 被视为退化后的函数指针类型 foo*
f(1); // 通过函数指针调用函数
}
@@ -183,6 +189,8 @@ int main() {
### `std::bind` 和 `std::placeholder`
+*(C++11)*
+
而 `std::bind` 则是用来绑定函数调用的参数的,
它解决的需求是我们有时候可能并不一定能够一次性获得调用某个函数的全部参数,通过这个函数,
我们可以将部分调用参数提前绑定到函数身上成为一个新的对象,然后在参数齐全后,完成调用。
@@ -190,13 +198,14 @@ int main() {
```cpp
int foo(int a, int b, int c) {
- ;
+ return a + b + c;
}
int main() {
- // 将参数1,2绑定到函数 foo 上,但是使用 std::placeholders::_1 来对第一个参数进行占位
- auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2);
+ // 将参数1,2绑定到函数 foo 上,
+ // 但使用 std::placeholders::_1 来对第一个参数进行占位
+ auto bindFoo = std::bind(foo, std::placeholders::_1, 1, 2);
// 这时调用 bindFoo 时,只需要提供第一个参数即可
- bindFoo(1);
+ std::cout << bindFoo(1) << std::endl; // 输出 4 } ``` @@ -209,38 +218,50 @@ int main() { 消除了诸如 `std::vector`、`std::string` 之类的额外开销, 也才使得函数对象容器 `std::function` 成为了可能。 -### 左值、右值的纯右值、将亡值、右值 +### 左值、右值、纯右值、将亡值 要弄明白右值引用到底是怎么一回事,必须要对左值和右值做一个明确的理解。 -**左值(lvalue, left value)**,顾名思义就是赋值符号左边的值。准确来说, +> 严格来说,**值类别 (value category) 是表达式的性质,而不是对象的性质**:每个 C++ 表达式都恰好属于三种基本值类别之一——左值 (lvalue)、纯右值 (prvalue)、将亡值 (xvalue)。下文中"左值是持久对象""右值是临时对象"一类的说法,是为了便于初学者建立直觉的近似描述;严谨的定义请参阅 [cppreference:值类别](https://zh.cppreference.com/w/cpp/language/value_category)。
+
+**左值** (lvalue, left value),顾名思义就是赋值符号左边的值。准确来说,
左值是表达式(不一定是赋值表达式)后依然存在的持久对象。
-**右值(rvalue, right value)**,右边的值,是指表达式结束后就不再存在的临时对象。
+**右值** (rvalue, right value),右边的值,是指表达式结束后就不再存在的临时对象。
而 C++11 中为了引入强大的右值引用,将右值的概念进行了进一步的划分,分为:纯右值、将亡值。
-**纯右值(prvalue, pure rvalue)**,纯粹的右值,要么是纯粹的字面量,例如 `10`, `true`;
+**纯右值** (prvalue, pure rvalue),纯粹的右值,要么是纯粹的字面量,例如 `10`, `true`;
要么是求值结果相当于字面量或匿名临时对象,例如 `1+2`。非引用返回的临时变量、运算表达式产生的临时变量、
原始字面量、Lambda 表达式都属于纯右值。
-需要注意的是,字符串字面量只有在类中才是右值,当其位于普通函数中是左值。例如:
+需要注意的是,字面量除了字符串字面量以外,均为纯右值。而字符串字面量是一个左值,类型为 `const char` 数组。例如:
```cpp
-class Foo {
- const char*&& right = "this is a rvalue"; // 此处字符串字面量为右值
-public:
- void bar() {
- right = "still rvalue"; // 此处字符串字面量为右值
- }
-};
+#include
int main() {
- const char* const &left = "this is an lvalue"; // 此处字符串字面量为左值
+ // 正确,"01234" 类型为 const char [6],因此是左值
+ const char (&left)[6] = "01234";
+
+ // 断言正确,确实是 const char [6] 类型,注意 decltype(expr) 在 expr 是左值
+ // 且非无括号包裹的 id 表达式与类成员表达式时,会返回左值引用
+ static_assert(std::is_same::value, "");
+
+ // 错误,"01234" 是左值,不可被右值引用
+ // const char (&&right)[6] = "01234";
}
```
-**将亡值(xvalue, expiring value)**,是 C++11 为了引入右值引用而提出的概念(因此在传统 C++ 中,
+但是注意,数组可以被隐式转换成相对应的指针类型,而转换表达式的结果(如果不是左值引用)则一定是个右值(右值引用为将亡值,否则为纯右值)。例如:
+
+```cpp
+const char* p = "01234"; // 正确,"01234" 被隐式转换为 const char*
+const char*&& pr = "01234"; // 正确,"01234" 被隐式转换为 const char*,该转换的结果是纯右值
+// const char*& pl = "01234"; // 错误,此处不存在 const char* 类型的左值
+```
+
+**将亡值** (xvalue, expiring value),是 C++11 为了引入右值引用而提出的概念(因此在传统 C++ 中,
纯右值和右值是同一个概念),也就是即将被销毁、却能够被移动的值。
将亡值可能稍有些难以理解,我们来看这样的代码:
@@ -267,6 +288,8 @@ std::vector v = foo();
### 右值引用和左值引用
+*(C++11)*
+
要拿到一个将亡值,就需要用到右值引用:`T &&`,其中 `T` 是类型。
右值引用的声明让这个临时值的生命周期得以延长、只要变量还活着,那么将亡值将继续存活。
@@ -293,7 +316,7 @@ int main()
const std::string& lv2 = lv1 + lv1; // 合法, 常量左值引用能够延长临时变量的生命周期
// lv2 += "Test"; // 非法, 常量引用无法被修改
- std::cout << lv2 << std::endl; // string,string + std::cout << lv2 << std::endl; // string,string, std::string&& rv2 = lv1 + lv2; // 合法, 右值引用延长临时对象生命周期 rv2 += "Test"; // 合法, 非常量引用能够修改临时变量 @@ -333,12 +356,14 @@ void foo() { ``` 由于 `int&` 不能引用 `double` 类型的参数,因此必须产生一个临时值来保存 `s` 的值, -从而当 `increase()` 修改这个临时值时,从而调用完成后 `s` 本身并没有被修改。 +从而当 `increase()` 修改这个临时值时,调用完成后 `s` 本身并没有被修改。 第二个问题,为什么常量引用允许绑定到非左值?原因很简单,因为 Fortran 需要。 ### 移动语义 +*(C++11)* + 传统 C++ 通过拷贝构造函数和赋值操作符为类对象设计了拷贝/复制的概念,但为了实现对资源的移动操作, 调用者必须使用先复制、再析构的方式,否则就需要自己实现移动对象的接口。 试想,搬家的时候是把家里的东西直接搬到新家去,而不是将所有东西复制一份(重买)再放到新家、 @@ -352,19 +377,19 @@ void foo() { class A { public: int *pointer; - A():pointer(new int(1)) { - std::cout << "构造" << pointer << std::endl; + A():pointer(new int(1)) { + std::cout << "构造" << pointer << std::endl; } - A(A& a):pointer(new int(*a.pointer)) { - std::cout << "拷贝" << pointer << std::endl; + A(A& a):pointer(new int(*a.pointer)) { + std::cout << "拷贝" << pointer << std::endl; } // 无意义的对象拷贝 - A(A&& a):pointer(a.pointer) { + A(A&& a):pointer(a.pointer) { a.pointer = nullptr; - std::cout << "移动" << pointer << std::endl; + std::cout << "移动" << pointer << std::endl; } - ~A(){ - std::cout << "析构" << pointer << std::endl; - delete pointer; + ~A(){ + std::cout << "析构" << pointer << std::endl; + delete pointer; } }; // 防止编译器优化 @@ -397,8 +422,8 @@ int main() { int main() { -std::string str = "Hello world."; -std::vector v;
+ std::string str = "Hello world.";
+ std::vector v;
// 将使用 push_back(const T&), 即产生拷贝行为
v.push_back(str);
@@ -418,6 +443,8 @@ std::vector v;
### 完美转发
+*(C++11)*
+
前面我们提到了,一个声明的右值引用其实是一个左值。这就为我们进行参数转发(传递)造成了问题:
```cpp
@@ -514,8 +541,8 @@ static_cast 传参: 右值引用
static_cast 传参: 左值引用
```
-无论传递参数为左值还是右值,普通传参都会将参数作为左值进行转发,
-所以 `std::move` 总会接受到一个左值,从而转发调用了`reference(int&&)` 输出右值引用。
+无论传递参数为左值还是右值,普通传参都会将参数作为左值进行转发;
+由于类似的原因,`std::move` 总会接受到一个左值,从而转发调用了`reference(int&&)` 输出右值引用。
唯独 `std::forward` 即没有造成任何多余的拷贝,同时**完美转发**(传递)了函数的实参给了内部调用的其他函数。
@@ -541,16 +568,42 @@ constexpr _Tp&& forward(typename std::remove_reference<_tp>::type&& __t) noexcep
```
在这份实现中,`std::remove_reference` 的功能是消除类型中的引用,
-而 `std::is_lvalue_reference` 用于检查类型推导是否正确,在 `std::forward` 的第二个实现中
+`std::is_lvalue_reference` 则用于检查类型推导是否正确,在 `std::forward` 的第二个实现中
检查了接收到的值确实是一个左值,进而体现了坍缩规则。
-当 `std::forward` 接受左值时,`_Tp` 被推导为左值,而所以返回值为左值;而当其接受右值时,
+当 `std::forward` 接受左值时,`_Tp` 被推导为左值,所以返回值为左值;而当其接受右值时,
`_Tp` 被推导为 右值引用,则基于坍缩规则,返回值便成为了 `&& + &&` 的右值。
可见 `std::forward` 的原理在于巧妙的利用了模板类型推导中产生的差异。
这时我们能回答这样一个问题:为什么在使用循环语句的过程中,`auto&&` 是最安全的方式?
因为当 `auto` 被推导为不同的左右引用时,与 `&&` 的坍缩组合是完美转发。
+### 强制复制消除
+
+*(C++17)*
+
+在 C++17 之前,编译器对于「用一个纯右值初始化对象」这种情形 *可以*(但不强制)省略复制/移动构造,这被称为复制消除 (copy elision)。由于它只是「允许」而非「保证」,被初始化对象的类型仍然必须拥有可访问的复制或移动构造函数,即便它实际上不会被调用。
+
+C++17 将这种情形下的复制消除变为 **强制 (guaranteed copy elision)**:当用一个同类型的纯右值初始化对象时,不再存在临时对象,对象被直接构造在目标位置上。因此,即便类型既不可复制也不可移动,按值返回一个纯右值也是合法的:
+
+```cpp
+struct NonMovable {
+ NonMovable() = default;
+ NonMovable(const NonMovable&) = delete; // 不可复制
+ NonMovable(NonMovable&&) = delete; // 不可移动
+};
+
+NonMovable make() {
+ return NonMovable{}; // C++17 合法:强制复制消除,无需复制/移动构造
+}
+
+int main() {
+ NonMovable n = make(); // 直接在 n 上构造
+}
+```
+
+上面的代码在 C++17 之前无法编译(需要可访问的移动或复制构造函数),而在 C++17 中是良构的。这使得工厂函数可以安全地返回不可移动的类型。
+
## 总结
本章介绍了现代 C++ 中最为重要的几个语言运行时的增强,其中笔者认为本节中提到的所有特性都是值得掌握的:
@@ -567,6 +620,6 @@ constexpr _Tp&& forward(typename std::remove_reference<_tp>::type&& __t) noexcep
## 许可
-知识共享许可协议
+知识共享许可协议
-本教程由[欧长坤](https://github.com/changkun)撰写,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。
+本教程由[欧长坤](https://github.com/changkun)撰写,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。
diff --git a/book/zh-cn/04-containers.md b/book/zh-cn/04-containers.md
index d39205db..d6521878 100644
--- a/book/zh-cn/04-containers.md
+++ b/book/zh-cn/04-containers.md
@@ -12,6 +12,8 @@ order: 4
### `std::array`
+*(C++11)*
+
看到这个容器的时候肯定会出现这样的问题:
1. 为什么要引入 `std::array` 而不是直接使用 `std::vector`?
@@ -100,6 +102,8 @@ std::sort(arr.begin(), arr.end());
### `std::forward_list`
+*(C++11)*
+
`std::forward_list` 是一个列表容器,使用方法和 `std::list` 基本类似,因此我们就不花费篇幅进行介绍了。
需要知道的是,和 `std::list` 的双向链表的实现不同,`std::forward_list` 使用单向链表进行实现,
@@ -108,6 +112,8 @@ std::sort(arr.begin(), arr.end());
## 4.2 无序容器
+*(C++11)*
+
我们已经熟知了传统 C++ 中的有序容器 `std::map`/`std::set`,这些元素内部通过红黑树进行实现,
插入和搜索的平均复杂度均为 `O(log(size))`。在插入元素时候,会根据 `<` 操作符比较元素大小并判断元素是否相同, 并选择合适的位置插入到容器中。当对这个容器中的元素进行遍历时,输出结果会按照 `<` 操作符的顺序来逐个遍历。 @@ -115,7 +121,7 @@ std::sort(arr.begin(), arr.end()); 而无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 `O(constant)`, 在不关心容器内部元素顺序时,能够获得显著的性能提升。 -C++11 引入了两组无序容器:`std::unordered_map`/`std::unordered_multimap` 和 +C++11 引入了的两组无序容器分别是:`std::unordered_map`/`std::unordered_multimap` 和 `std::unordered_set`/`std::unordered_multiset`。 它们的用法和原有的 `std::map`/`std::multimap`/`std::set`/`set::multiset` 基本类似, @@ -174,6 +180,8 @@ Key:[3] Value:[3] ### 元组基本操作 +*(C++11)* + 关于元组的使用有三个核心的函数: 1. `std::make_tuple`: 构造元组 @@ -295,6 +303,123 @@ for(int i = 0; i != tuple_len(new_tuple); ++i) std::cout << tuple_index(new_tuple, i) << std::endl; ``` +不过,上面这种「先实现运行期索引、再逐个索引」的遍历方式虽然可行,却相当迂回。如果只是想对元组的每个元素施加同一个操作,更直接、惯用的做法是借助 `std::index_sequence`(C++14 引入)在编译期展开下标。在 C++17 中可以配合折叠表达式写成: + +```cpp +template
+void iterate_impl(Func&& f, Tuple&& tpl, std::index_sequence) {
+ (f(std::get(std::forward(tpl))), ...);
+}
+template
+void iterate_tuple(Func&& f, Tuple&& tpl) {
+ iterate_impl(std::forward(f), std::forward(tpl),
+ std::make_index_sequence>>{});
+}
+```
+
+到了 C++20,还可以利用允许显式书写模板参数的 Lambda,把辅助函数也一并省去:
+
+```cpp
+template
+void iterate_tuple(Func f, const std::tuple& tpl) {
+ [&](std::index_sequence) {
+ (f(std::get(tpl)), ...);
+ }(std::make_index_sequence