├── 01-Intro ├── .clang-format ├── README.md └── 现代C++基础 - 简介(新).pdf ├── 02-Basics ├── 1.jpg ├── 2.jpg ├── README.md └── 现代C++基础 - 基础复习与扩展.pdf ├── 03-Containers ├── README.md └── 现代C++基础 - 容器,ranges与算法 - Part1.pdf ├── 04-Ranges & Algorithms ├── Answer.md ├── README.md ├── examples │ ├── 01-keys.cpp │ ├── 02-zip.cpp │ ├── 03-split.cpp │ ├── 04-caching.cpp │ ├── 05-transform.cpp │ ├── 06-to.cpp │ └── 07-functional.cpp └── 现代C++基础 - 容器,ranges与算法 - Part2.pdf ├── 05-Lifetime & Type Safety ├── Answer.md ├── README.md └── 现代C++基础 - 生命周期与类型安全.pdf ├── 06-Programming in Multiple Files ├── Answer.md ├── README.md ├── code │ ├── example1 │ │ ├── Vector3.cpp │ │ ├── Vector3.h │ │ ├── func.cpp │ │ ├── func.h │ │ ├── main.cpp │ │ ├── namespace.cpp │ │ ├── namespace.h │ │ ├── singleton.cpp │ │ ├── singleton.h │ │ ├── template.cpp │ │ └── template.h │ ├── extern-C-test │ │ ├── test1 │ │ │ ├── a.cpp │ │ │ ├── a.h │ │ │ └── main.c │ │ ├── test2 │ │ │ ├── a.cpp │ │ │ ├── a.h │ │ │ ├── a.hpp │ │ │ └── main.c │ │ └── xmake.lua │ ├── inline-example │ │ ├── func.cpp │ │ ├── func.h │ │ └── main.cpp │ ├── inline-test │ │ ├── DLLMacro.h │ │ ├── func.cpp │ │ ├── func.h │ │ ├── main.cpp │ │ ├── test.h │ │ └── xmake.lua │ ├── linkage-test │ │ ├── a.cpp │ │ ├── a.h │ │ ├── main.cpp │ │ └── template.cpp │ └── xmake.lua ├── modules │ ├── CMakeLists.txt │ ├── module-test-20 │ │ ├── CMakeLists.txt │ │ ├── global-fragment │ │ │ ├── Old.cpp │ │ │ ├── Old.h │ │ │ ├── Person.cpp │ │ │ ├── Person.mpp │ │ │ └── main.cpp │ │ ├── header-unit │ │ │ ├── Old.cpp │ │ │ ├── Old.h │ │ │ ├── Person.cpp │ │ │ ├── Person.mpp │ │ │ └── main.cpp │ │ ├── partition │ │ │ ├── Person-Order.mpp │ │ │ ├── Person-Utils.mpp │ │ │ ├── Person.cpp │ │ │ ├── Person.mpp │ │ │ └── main.cpp │ │ ├── private-fragment │ │ │ ├── Person.mpp │ │ │ └── main.cpp │ │ ├── simple │ │ │ ├── Customer.mpp │ │ │ ├── Person.mpp │ │ │ └── main.cpp │ │ ├── split-impl │ │ │ ├── Person-Funcs.cpp │ │ │ ├── Person.cpp │ │ │ ├── Person.mpp │ │ │ └── main.cpp │ │ └── xmake.lua │ ├── module-test │ │ ├── CMakeLists.txt │ │ ├── global-fragment │ │ │ ├── Old.cpp │ │ │ ├── Old.h │ │ │ ├── Person.cpp │ │ │ ├── Person.mpp │ │ │ ├── README.md │ │ │ └── main.cpp │ │ ├── header-unit │ │ │ ├── Old.cpp │ │ │ ├── Old.h │ │ │ ├── Person.cpp │ │ │ ├── Person.mpp │ │ │ ├── README.md │ │ │ └── main.cpp │ │ ├── partition │ │ │ ├── Person-Order.mpp │ │ │ ├── Person-Utils.mpp │ │ │ ├── Person.cpp │ │ │ ├── Person.mpp │ │ │ ├── README.md │ │ │ └── main.cpp │ │ ├── private-fragment │ │ │ ├── Person.mpp │ │ │ ├── README.md │ │ │ └── main.cpp │ │ ├── simple │ │ │ ├── Customer.mpp │ │ │ ├── Person.mpp │ │ │ └── main.cpp │ │ ├── split-impl │ │ │ ├── Person-Funcs.cpp │ │ │ ├── Person.cpp │ │ │ ├── Person.mpp │ │ │ ├── README.md │ │ │ └── main.cpp │ │ └── xmake.lua │ └── xmake.lua └── 现代C++基础 - 多文件编程.pdf ├── 07-Error Handling ├── Answer.md ├── README.md ├── code │ ├── copy-and-swap idiom.cpp │ ├── expected.cpp │ ├── optional.cpp │ └── xmake.lua └── 现代C++基础 - 错误处理.pdf ├── 08-String & Stream ├── Answer.md ├── README.md ├── code │ ├── Decompose.cpp │ ├── Profile.cpp │ ├── RegexTest.cpp │ └── xmake.lua ├── example-answer │ ├── src │ │ ├── Socket.cpp │ │ ├── Socket.h │ │ ├── TCPBuf.h │ │ ├── TCPStream.h │ │ ├── client.cpp │ │ └── server.cpp │ └── xmake.lua ├── example │ ├── src │ │ ├── Socket.cpp │ │ ├── Socket.h │ │ ├── TCPBuf.h │ │ ├── TCPStream.h │ │ ├── client.cpp │ │ └── server.cpp │ └── xmake.lua └── 现代C++基础 - 流与字符串.pdf ├── 09-Move Semantics Basics ├── Answer.md ├── README.md ├── code │ ├── MyIntVector-sac.h │ └── MyIntVector.h └── 现代C++基础 - Move Semantics Basics.pdf ├── 10-Value Category and Move Semantics ├── Answer.md ├── README.md ├── code │ └── RVO and NRVO.cpp └── 现代C++基础 - 值分类与移动语义.pdf ├── 11-Template Basics and Move Semantics ├── Answer.md ├── README.md └── 现代C++基础 - 模板基础与移动语义.pdf ├── 12-Advanced Template ├── Answer-code │ ├── FixedString.cpp │ ├── Function-SBO.cpp │ ├── Function.cpp │ ├── Invoke.cpp │ └── PushBackGuard.cpp ├── Answer.md ├── README.md ├── code │ ├── CRTP and Type Erasure │ │ ├── CRTP-deducing this.cpp │ │ ├── CRTP.cpp │ │ └── Type Erasure.cpp │ ├── Lazy.cpp │ ├── NTTP.cpp │ ├── SFINAE │ │ ├── DefaultConstructible.cpp │ │ ├── Dictionary.cpp │ │ ├── advance.cpp │ │ └── conditional.cpp │ └── Variadic Template │ │ ├── Ex1-3.cpp │ │ ├── Ex4-6.cpp │ │ ├── Ex7-9.cpp │ │ └── print.h └── 现代C++基础 - 模板进阶.pdf └── README.md /01-Intro/.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Microsoft 2 | BreakAfterAttributes: Leave 3 | ColumnLimit: 80 4 | Cpp11BracedListStyle: false 5 | AllowShortFunctionsOnASingleLine: Inline 6 | AlignEscapedNewlines: Left 7 | AccessModifierOffset: -4 8 | AlwaysBreakTemplateDeclarations: Yes 9 | RequiresClausePosition: OwnLine 10 | SpaceAfterTemplateKeyword: false 11 | # AllowShortCompoundRequirementOnASingleLine : true -------------------------------------------------------------------------------- /01-Intro/README.md: -------------------------------------------------------------------------------- 1 | # 01-Intro 2 | 1. 在Visual Studio中,你可能需要Ctrl+K后Ctrl+F的组合键来使文档特定部分格式化。 3 | 2. 补充[一个网站](https://hackingcpp.com/),上面有一些cheat sheet,可以参考。 4 | 3. 如果不想用提供的format,可以参考https://clang.llvm.org/docs/ClangFormatStyleOptions.html。 5 | 6 | 这节课没作业,就把讲过的功能玩一玩、网站收藏一下就行了。 -------------------------------------------------------------------------------- /01-Intro/现代C++基础 - 简介(新).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/01-Intro/现代C++基础 - 简介(新).pdf -------------------------------------------------------------------------------- /02-Basics/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/02-Basics/1.jpg -------------------------------------------------------------------------------- /02-Basics/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/02-Basics/2.jpg -------------------------------------------------------------------------------- /02-Basics/README.md: -------------------------------------------------------------------------------- 1 | # 02-Basics 2 | 3 | ### 上半部分 4 | 5 | 1. 使用gcc(善用compiler explorer)编译并运行下面的程序: 6 | 7 | ```c++ 8 | #include 9 | int main() 10 | { 11 | int a = std::numeric_limits::max(); 12 | int b = a + 1; 13 | return 0; 14 | } 15 | ``` 16 | 17 | 随后尝试加上编译选项`-ftrapv`,看看运行结果有没有变化。 18 | 19 | 2. 打印一个`std::uint8_t`的变量的地址;如果你想用`print`,可以使用`std::println("{}", 你的地址)`。 20 | 21 | 3. 在2001年的游戏《雷神之锤3》(Quake III Arena)中,使用了一种快速倒数平方根算法,用于对于32位浮点数`x`计算$1/\sqrt x$的近似值。它的算法如下: 22 | 23 | ```pseudocode 24 | /* 设操作BitEquiv表示两个数在二进制表示上相同, i32表示32位整数,f32表示32位浮点数。*/ 25 | f32 GetInvSqrt(f32 num) 26 | { 27 | i32 a <- BitEquiv(num); 28 | i32 b <- 0x5f3759df - (a >> 1); 29 | f32 y <- BitEquiv(b); 30 | return y * (1.5f - (num * 0.5f * y * y)); 31 | } 32 | ``` 33 | 34 | 试把上述伪代码转为C++代码。可以尝试几个数,看看和``中的`std::sqrtf`相比的误差;如果你感兴趣,也可以在quick-bench上比一比性能。 35 | 36 | > 当然,这个算法在目前是比CPU硬件指令更慢的,只是在2000年代更好。它本质上使用了牛顿迭代法,`return`的步骤进行了一次迭代,如果想要更高的精度可以把它赋给`y`,继续用该式迭代。 37 | 38 | 4. 写一个将二进制数据转为HTTP要求的字节序的函数。注意用`std::endian`区分当前机器大小端的情况;如果两个都不是,打印一个警告。 39 | 40 | 5. 判断以下程序的合法性: 41 | 42 | ```c++ 43 | int a[][3] = { {1, 2, 3}, {4,5,6} }; 44 | int (*b)[3] = a; // 合法吗? 45 | int **c = a; // 合法吗? 46 | int **d = b; // 合法吗? 47 | ``` 48 | 49 | ```c++ 50 | int a[]{1,2,3}, b[]{4,5,6,7}; 51 | void Func(int (&a)[3]) { /* ... */} 52 | void Func2(int a[3]) { /* ... */} 53 | 54 | Func(a); Func2(a); 55 | Func(b); Func2(b); // 这四个里面哪个合法? 56 | ``` 57 | 58 | 希望你可以通过这个例子区分指针和数组,加深对数组decay的理解;指针所指向的目标不会继续decay,引用所引用的目标也会暂时保持原类型(不过仍然允许后续的decay,比如上面的`Func`中`int* p = a;`是合法的)。 59 | 60 | 6. 写一个函数,它接受一个返回值为`int`、参数为`float`和`double`的函数指针为参数,返回`void`。 61 | 62 | 7. 写一个scoped enumeration,它包含`read, write, exec`三个选项;同时为了使它们可以按位组合,编写一个配套的重载的按位与和按位或的运算符。为了一般性,可以使用`auto b = std::to_underlying()`;如果你的编译器不支持,可以使用`using T = std::underlying_type`,再手动转为`T`。 63 | 64 | 8. 对于下面的程序: 65 | 66 | ```c++ 67 | #include 68 | int main() 69 | { 70 | std::map m; 71 | m[0] = m.size(); 72 | } 73 | ``` 74 | 75 | 按照C++17标准规定,`m[0]`是什么? 76 | 77 | ## 后半部分 78 | 79 | 1. 写一个`Vector3`类,它还有三个float分量,可以用不超过3个float进行初始化,例如: 80 | 81 | ```c++ 82 | Vector3 vec; // (0, 0, 0) 83 | Vector3 vec2{1}; // (1, 0, 0) 84 | Vector3 vec3{1, 2}; // (1, 2, 0) 85 | ``` 86 | 87 | 同时,定义`operator+`和`operator+=`。试一试,`1+a`在`operator+`写成成员函数时是否可用?写成全局函数时呢?给构造函数加上`explicit`时呢? 88 | 89 | > 这是因为`1`作为函数的参数,进行的是copy initialization,这正是explicit所禁止的;必须写为`Vector3{1}`才可以。 90 | 91 | 随后,我们增加比较运算符,比较两个向量的长度。用简短的方法使六个比较运算符都有效。 92 | 93 | 别忘了用属性警告用户某些运算符的返回值不应被抛弃。 94 | 95 | 2. 除了`vector`,其他的容器可以使用Uniform initialization吗?特别地,`map`和`unordered_map`每个元素是一个pair。 96 | 97 | 3. 有虚函数的类和普通的类相比,有大小上的差别吗? 98 | 99 | 4. 下面的两个函数构成重载吗? 100 | 101 | ```c++ 102 | void Test(int); 103 | int Test(int); 104 | ``` 105 | 106 | 下面两个呢?`T = void`会有干扰吗? 107 | 108 | ```c++ 109 | void Test(int); 110 | template T Test(int); 111 | ``` 112 | 113 | 想一想name mangling的规则,得出你的结论,在编译期上进行测试。 114 | 115 | 5. 下面的程序中,`bar`返回什么是确定的吗?分别在gcc无优化选项和`-O2`中试试。 116 | 117 | ```c++ 118 | struct Foo 119 | { 120 | int foo() 121 | { 122 | return this ? 42 : 0; 123 | } 124 | }; 125 | 126 | int bar() 127 | { 128 | Foo* fptr = nullptr; 129 | return fptr->foo(); 130 | } 131 | ``` 132 | 133 | 这道题的目的是告诉你不要写UB,空指针不能解引用。 134 | 135 | 6. 写一个三维数组,以存储的类型为模板参数,内部使用摊平的一维连续数组存储,允许使用多维下标运算符进行访问。定义它的拷贝构造函数,想想是否要考虑自赋值问题。 136 | 137 | 7. 写一个函数,它接受`std::vector&`,使用``中的`std::ranges::generate`在其中填充斐波那契数列。你只需要填写第二个参数,即一个lambda表达式: 138 | 139 | ```c++ 140 | void FillFibonacci(std::vector& v) 141 | { 142 | std::ranges::generate(v, /* 你的回答 */); 143 | } 144 | ``` 145 | 146 | 可以认为上面的式子相当于: 147 | 148 | ```c++ 149 | for(auto& elem : v) 150 | elem = YourLambda(); 151 | ``` 152 | 153 | > 提示:你可以在capture中创建新的变量,例如`[a = 0, b = 1]`。 154 | 155 | 8. 定义一个scoped enumeration `Color`,有red, blue, green三个枚举量。编写一个`switch`,使得`red`和`green`都打印`Hello`,对`blue`打印`World`。使用`using enum`来避免每个枚举都写`Color::`,并使用属性防止编译器对fallthrough给出警告。 156 | 157 | 9. 对下面的结构体的数组调用sort,使用lambda表达式进行比较。 158 | 159 | ```c++ 160 | struct DijkstraInfo 161 | { 162 | int vertexID; 163 | int distance; // 作为比较的标准 164 | }; 165 | 166 | std::ranges::sort(vec, /* 你的lambda表达式 */ ); 167 | ``` 168 | 169 | 想一想,是否可以把两个参数类型都写成`const auto&`? 170 | 171 | 如果你写成一个函数: 172 | 173 | ```c++ 174 | bool Func(const auto& a, const auto& b); 175 | ``` 176 | 177 | 它能否像lambda表达式一样传入`sort`?还是需要实例化?为什么? 178 | 179 | 如果你写成一个下面的结构体: 180 | 181 | ```c++ 182 | template 183 | struct Functor 184 | { 185 | // 如果你的编译器暂时不支持static operator(),把static去掉. 186 | static bool operator()(const T& a, const T& b){ /* ... */ } 187 | }; 188 | ``` 189 | 190 | 它能否像lambda表达式一样传入`sort`?还是需要实例化?为什么? 191 | 192 | 希望你通过这个例子理解模板实例化的要求, 可以通过包装在无需实例化的实体中绕过限制, 推迟到调用的时候再实例化, 此时就不需要程序员手动实例化. 193 | 194 | 10. 解答Lee的问题; 你应该如果解决? 195 | 196 | ![](2.jpg) 197 | 198 | ![](1.jpg) -------------------------------------------------------------------------------- /02-Basics/现代C++基础 - 基础复习与扩展.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/02-Basics/现代C++基础 - 基础复习与扩展.pdf -------------------------------------------------------------------------------- /03-Containers/README.md: -------------------------------------------------------------------------------- 1 | # 03-Containers 2 | 3 | ### 上半部分 4 | 5 | 1. 写一个模板函数,对第一个元素`*2`,并累加所有的元素: 6 | 7 | ```c++ 8 | template 9 | void Function(Range& range) 10 | { 11 | } 12 | // 请测试以下情况: 13 | std::vector v{1,2,3}; Function(v); 14 | std::list l{1,2,3}; Function(l); 15 | int arr[]{1,2,3}; Function(arr); // 看到这里,你就应该明白不能使用.begin,而是使用什么? 16 | ``` 17 | 18 | 2. 对`vector`做一些简单的API练习(本题中输出`vector`请包装为一个函数,并用`ostream_iterator`输出): 19 | 20 | + 创建一个`std::vector v1`,它包含10个5(请不要真的写上去`{5,5,5,5,5...}`)。 21 | + 创建第二个`std::vector v2`,包含`{2,1,4}`。 22 | + 将`v2`反向插入到`v1`的第三个位置(即`5,5,4,1,2,5,5...`)。注意使用反向迭代器。 23 | + 移除从第5个元素开始的所有元素(即`5,5,4,1,2`)。 24 | + 移除所有奇数,请不要使用`O(n^2)`的方法。 25 | + 与`vector v{1,7}`做三路比较,打印结果。 26 | 27 | 3. 对`list`做一些简单的API练习: 28 | 29 | + 创建一个`std::list l1`,它包含10个5。 30 | + 创建第二个`std::list l2`,包含`{2,1,4}`。 31 | + 将`l2`融合到`l1`中,操作后`l2`变为空。 32 | + 对`l1`去除相邻的重复元素。 33 | + 对`l1`排序。 34 | 35 | 4. `deque`是否具有`.data`方法?为什么? 36 | 37 | 5. 回忆各个容器的迭代器失效问题。下面的代码有什么问题? 38 | 39 | ```c++ 40 | auto it = vec.find(1); 41 | vec.insert(it - 1, 110); 42 | *it = 2; 43 | ``` 44 | 45 | 聪明的你发现了在前面插入有迭代器失效问题,那么下面的代码呢? 46 | 47 | ```c++ 48 | auto it = vec.find(1); 49 | vec.insert(it + 1, 110); 50 | *it = 2; 51 | ``` 52 | 53 | 如果你没有思路,看看下面的代码是否合法: 54 | 55 | ```c++ 56 | auto it = vec.find(1); 57 | if(it.capacity() > it.size()) 58 | { 59 | vec.insert(it + 1, 110); 60 | *it = 2; 61 | } 62 | ``` 63 | 64 | 6. 小明写了下面的代码,期望删除vector的最大值: 65 | 66 | ```c++ 67 | std::vector vec{ 10,2,3,10,4 }; 68 | auto it = std::ranges::max_element(vec); 69 | std::erase(vec, *it); 70 | for (auto& i : vec) 71 | std::print("{}", i); 72 | ``` 73 | 74 | 运行一下,发现并没有移除所有的`10`;这段代码有什么问题?特别提示,`std::erase`接受的是引用,你可以理解成一直在移除掉`*it`,请注意擦除过程中迭代器下面的内容是否发生了变化。 75 | 76 | 解决方法就是拷贝一下,即`auto m = *it`,或者C++23里直接`std::erase(vec, auto{*it})`,又或者使用`std::ranges::max`,直接返回值类型而不是迭代器。 77 | 78 | > 注意迭代器失效和引用失效的最重要方法就是看是否在获得迭代器到使用迭代器的过程中容器本身是否修改。 -------------------------------------------------------------------------------- /03-Containers/现代C++基础 - 容器,ranges与算法 - Part1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/03-Containers/现代C++基础 - 容器,ranges与算法 - Part1.pdf -------------------------------------------------------------------------------- /04-Ranges & Algorithms/Answer.md: -------------------------------------------------------------------------------- 1 | 1. 2 | ```c++ 3 | #include 4 | #include 5 | 6 | namespace stdr = std::ranges; 7 | namespace stdv = std::views; 8 | 9 | template 10 | Cont QuickSort(const Cont &cont) 11 | { 12 | // 如果是空,返回空容器;否则... 13 | return stdr::empty(cont) 14 | ? cont 15 | : std::array{ 16 | QuickSort(cont | stdv::drop(1) | stdv::filter([&](const auto &elem) { 17 | return elem < *stdr::begin(cont); 18 | }) | stdr::to()), 19 | stdv::single(*stdr::begin(cont)) | stdr::to(), 20 | QuickSort(cont | stdv::drop(1) | stdv::filter([&](const auto &elem) { 21 | return elem >= *stdr::begin(cont); 22 | }) | stdr::to()), 23 | } | stdv::join | stdr::to(); 24 | } 25 | ``` 26 | 27 | 2. 28 | ```c++ 29 | std::vector SplitString(const std::string &str, 30 | const std::string &delim) 31 | { 32 | // 或者lazy_split,在这道题里没有本质区别。 33 | return str | std::views::split(delim) | std::views::transform([](auto rng) { 34 | return rng | std::ranges::to(); 35 | }) | 36 | std::ranges::to(); 37 | } 38 | 39 | int main() 40 | { 41 | for (auto s : SplitString("a,b,c,d", ",")) 42 | std::print("{}\n", s); 43 | return 0; 44 | } 45 | ``` 46 | 47 | 3. 48 | ```c++ 49 | for(auto [idx, elem] : str | stdv::enumerate) 50 | elem = (elem - 'a' + idx) % 26 + 'a'; 51 | ``` 52 | 4. 53 | ```c++ 54 | #include 55 | #include 56 | 57 | std::experimental::generator Fib(int num) 58 | { 59 | int a = 0, b = 1; 60 | for (int i = 0; i < num; i++) 61 | { 62 | int temp = a; 63 | a = b; 64 | b += temp; 65 | co_yield a; 66 | } 67 | } 68 | 69 | int main() 70 | { 71 | auto fib5 = Fib(5); 72 | for (auto elem : fib5) 73 | std::print("{} ", elem); 74 | return 0; 75 | } 76 | ``` -------------------------------------------------------------------------------- /04-Ranges & Algorithms/README.md: -------------------------------------------------------------------------------- 1 | # Errata 2 | 1. 例子出现位置在19:30 (example 01)、26:40 (example 02)、40:15 (example 04) 46:50 (example 05)、51:25 (example 06) 3 | 4 | 2. 16:40口误,不是做引用,而是做“修改”;auto&和const auto&都是指一个东西(对于不存在的序列本质上也可以用const auto&,不过引用的对象是临时对象,因此不能使用auto&);后面说的被引用也是“通过引用修改”的含义。一个例子就可以说明区别: 5 | ```c++ 6 | // 这里也可以使用const auto&,但是不能使用auto&,因为返回的是临时值 7 | for(auto ele1: stdv::iota(1, 10)) {} 8 | 9 | const std::vector v{1,2,3}; 10 | // 这里既可以使用auto&,也可以使用const auto&,不过二者都是相同的const int&,因为面对的是非临时的只读对象 11 | for(auto ele1: v){ } 12 | ``` 13 | 14 | 3. 38:40-39:40,所有“lazy_split_view对iterator解引用的时候”,都是指“对iterator解引用的view进行迭代的时候”,请见github的example 03 15 | 16 | 4. 44:05不是对list,是对vector保留cache 17 | 18 | 5. 事实上`stdr::to`既可以接受一个模板作为模板参数(例如`std::vector`),也可以接受一个类型作为模板参数(例如`std::vector`)。 19 | 20 | 6. `pop_heap`保证被pop出来的元素在原来的最后一个位置(即`[first, last - 1)`构成新的堆,而`last - 1`是原来的堆顶),并不是invalid。 21 | 22 | # Assignment 23 | 24 | ## Ranges 25 | 学完ranges这一部分,你肯定有很多想写的代码。我们也只是抛砖引玉,写一些比较有意思的东西。 26 | 27 | 1. 对于可以用`stdr::to`构造的容器,用一条语句完成快速排序(即只有一个return)。 28 | 29 | ```c++ 30 | template 31 | Cont QuickSort(const Cont& cont) 32 | { 33 | // 如果是空,返回空容器;否则... 34 | return stdr::empty(cont) ? cont : 35 | // Your code... 36 | } 37 | ``` 38 | 39 | > Hint:简单回顾一下快速排序的步骤: 40 | > 41 | > + 取一个轴点,不妨是第一个元素。注意不要用`operator[]`,因为容器不保证随机访问。不过可以考虑使用`stdr::b...`取得迭代器(差点就告诉你了)。 42 | > + 不考虑轴点的位置,把所有小于轴点的部分进行QuickSort,大于等于轴点的部分也进行QuickSort,在把三个部分拼在一起即可。 43 | > + 对于轴点,可以使用`stdv::single(...)`来获得它的view,再用`stdr::to`来转型为容器。 44 | > + 把它们组合成一个`std::array`,随后有一个什么view可以把多个view拼成一个view来着? 45 | 46 | 这种方式并不是很高效,但是有些语言(例如Python)以此宣传自己功能的强大,事实上C++利用ranges也可以做到。 47 | 48 | 2. 让我们来分割字符串吧!C++中的`std::string`不存在字符串分割函数一直饱受诟病,被Python等语言使用者嘲笑,把ranges甩给他们吧! 49 | 50 | > 我并不是对Python有什么意见,因为我自己也是Python使用者( 51 | 52 | ```c++ 53 | // 函数参数使用std::string_view更好一点,不过我们还没讲到流与字符串,所以就略过了。 54 | // 可以简单说一下,他和span十分相似,存储了两个const char指针,代表了一段字符范围。 55 | std::vector SplitString(const std::string& str, 56 | const std::string& delim) 57 | { 58 | return str | // Your code. 59 | } 60 | ``` 61 | 62 | 注意,分割相关的view得到的view并不直接是`std::string`,需要先transform成`std::string`。 63 | 64 | > C++中不存在字符串分割函数的原因是不知道用什么存储它比较合适,比如我们选择了`std::vector`事实上导致了大量的动态内存分配。有了ranges就没这个顾虑了,反正是惰性求值,具体存在哪由用户决定就行了,对标准库无负担。 65 | 66 | 3. 进行简单的加密:对一个小写英文字母的字符串,减去`'a'`,再加上它的下标并mod 26(不考虑溢出问题),再加上`'a'`来获得密文;例如`abc`会变为`ace`。 67 | 68 | ```c++ 69 | for(auto [idx, elem] : /* Your code */ ) 70 | elem = /* Your code */ 71 | ``` 72 | 73 | 4. 利用Generator生成Fibonacci数列,如下: 74 | 75 | ```c++ 76 | // 目前只有gcc14对应的libstdc++支持std::generator;对于msvc用户,可以先用中的std::experimental::generator. 77 | std::generator Fib(int num) // 生成[0, num)的斐波那契数列 78 | { 79 | 80 | } 81 | 82 | auto fib5 = Fib(5); 83 | for(auto num : fib5) 84 | std::print("{} ", num); // 1, 1, 2, 3, 5 85 | ``` 86 | 87 | 88 | ## Function 89 | 90 | 1. 猜测一下为什么`std::bind_back/front`的绑定需要`std::ref`才能真正地把引用绑定上去;这和move-only的对象`bind`后不能再调用的原因密切相关。 91 | 92 | > Hint:move-only不能调用,说明在函数调用的过程中发生了拷贝;那么绑定的对象到函数的参数其实是一个...(答案呼之欲出啊) 93 | > 94 | > 所以,`std::reference_wrapper`由于可以...,因此这次拷贝相当于把原来的参数绑定在了引用上。 95 | 96 | 2. 测试一下transparent operator相关的优化: 97 | 98 | ```c++ 99 | class A 100 | { 101 | public: 102 | A(int init_a) : a{ init_a } { std::println("Construction: a = {}", a); } 103 | // 这里写一下默认的三路比较运算符和== 104 | 105 | // 这里写一下与int比较的三路比较运算符和== 106 | 107 | private: // 我们用int代表一个昂贵的构造对象。 108 | int a; 109 | }; 110 | 111 | int main() 112 | { 113 | /* 以A为key的std::set类型 */ normalSet{ A{ 1 }, A{ 2 }, A{ 3 } }; 114 | /* 同上,但支持transparent operator的类型 */ goodSet{ A{ 1 }, A{ 2 }, A{ 3 } }; 115 | 116 | std::println("Begin testing..."); 117 | std::println("----For normal set."); 118 | normalSet.find(1); 119 | 120 | std::println("----For good set."); 121 | goodSet.find(1); 122 | 123 | return 0; 124 | } 125 | ``` 126 | 127 | 看看输出,是不是真的少了一次构造。 128 | 129 | 3. 既然`std::function`相关的类有性能损失,为什么不总是使用模板呢?构想一个简单的任务,它使用模板会非常不方便去存储各种函数体。 130 | 131 | > 提示:想想什么时候必须要统一类型? 132 | -------------------------------------------------------------------------------- /04-Ranges & Algorithms/examples/01-keys.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace stdr = std::ranges; 6 | namespace stdv = std::views; 7 | 8 | int main() 9 | { 10 | std::map m{ { 1, 2.0 }, { 3, 4.0 } }; 11 | // 如果是auto&:由于map的key是const的,key的类型为const int& 12 | for (auto key : m | stdv::keys) 13 | std::print("{} ", m); 14 | return 0; 15 | } -------------------------------------------------------------------------------- /04-Ranges & Algorithms/examples/02-zip.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace stdr = std::ranges; 7 | namespace stdv = std::views; 8 | 9 | int main() 10 | { 11 | std::vector v{ 1, 2, 3 }; 12 | std::list l{ 4, 5, 6, 7 }; 13 | 14 | for (auto [ele1, ele2] : stdv::zip(v, l)) 15 | { 16 | std::println("{} {} {}", ele1, ele2, ele1 + ele2); 17 | ele1 = ele2; // vector本身的元素也被修改 18 | } 19 | 20 | for (auto [ele1, ele2] : stdv::zip(v, l)) 21 | { // 验证 22 | std::println("{} {} {}", ele1, ele2, ele1 + ele2); 23 | } 24 | 25 | return 0; 26 | } -------------------------------------------------------------------------------- /04-Ranges & Algorithms/examples/03-split.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct A 7 | { 8 | int a; 9 | auto operator<=>(const A &another) const 10 | { 11 | std::print("Hello?"); 12 | return a <=> another.a; 13 | } 14 | auto operator==(const A &another) const 15 | { 16 | std::print("Hello2?"); 17 | return a == another.a; 18 | } 19 | }; 20 | 21 | int main() 22 | { 23 | std::vector source{ A{ 1 }, A{ 2 }, A{ 3 }, A{ 0 }, A{ 4 }, A{ 5 }, 24 | A{ 6 }, A{ 0 }, A{ 7 }, A{ 8 }, A{ 9 } }; 25 | A delimiter{ 0 }; 26 | std::ranges::lazy_split_view outer_view{ source, delimiter }; 27 | std::ranges::split_view outer_view2{ source, delimiter }; 28 | 29 | std::println("For lazy split...."); 30 | auto it = outer_view.begin(); // 无判断发生 31 | std::println("No search when getting iterator...comparision begin at " 32 | "iterating the result view."); 33 | for (auto m : *it) // 对*it迭代,才会进行判断。 34 | std::print("{} ", m.a); 35 | // 为什么*it(即lazy_split_view产生的view)只能是forward_range呢?因为它 36 | // 需要不断地判断是否等于delimiter,才能知道是否到达了end,因此它只能从前向后不断迭代。 37 | // 再次迭代*it,还是会重新判断一遍。 38 | 39 | std::println("For split...."); 40 | auto it2 = outer_view2.begin(); // 已经开始进行判断了。 41 | std::println("\nAfter searching...no comparision is needed."); 42 | for (auto m : *it2) // 无判断发生 43 | std::print("{} ", m.a); 44 | // 为什么*it2(即split_view产生的view)可以保持原来view的category呢?因为它 45 | // 早已判断好了end的位置。 46 | // 再次迭代*it2,不会重新判断了。 47 | 48 | // 例如对于random access range,split里*it2完全知道了范围[begin, 49 | // end),可以使用auto n = (*it2).begin(); n[index]访问;而lazy_split_view 50 | // 不能确定end,因此没法提供random access 51 | // (否则无法知道n[index]是否超过了end,对吧?),只能边迭代边判断。 52 | return 0; 53 | } -------------------------------------------------------------------------------- /04-Ranges & Algorithms/examples/04-caching.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | std::vector v{ 2, 4, 3, 5 }; 9 | auto rng = v | std::views::filter([](auto num) { return num % 2 == 1; }); 10 | // 目前仅libc++支持C++23的整个range打印,其他编译器请用for(auto elem : rng) 11 | std::println("{}", rng); // 3, 5 12 | v[0] = 1, v[2] = 2; // 序列变为{ 1, 4, 2, 5 },期望输出1, 5 13 | std::println("{}", rng); // 2, 5 ??? 14 | return 0; 15 | } -------------------------------------------------------------------------------- /04-Ranges & Algorithms/examples/05-transform.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | std::vector v{ 2, 4, 3, 5 }; 9 | for (auto elem : v | std::views::filter([](auto num) { 10 | return num % 2 == 1; 11 | }) | std::views::transform([](int a) { return a * 2; })) 12 | std::println("{}", elem); // 6,10 13 | return 0; 14 | } -------------------------------------------------------------------------------- /04-Ranges & Algorithms/examples/06-to.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace stdr = std::ranges; 7 | namespace stdv = std::views; 8 | 9 | int main() 10 | { 11 | std::map m{ { 1, 2.0 }, { 3, 4.0 } }; 12 | // Nope: auto view = m | stdv::values; std::println("{}", view[1]); 13 | auto valueVec = m | stdv::values | stdr::to(); 14 | std::println("{} ", valueVec[1]); 15 | return 0; 16 | } -------------------------------------------------------------------------------- /04-Ranges & Algorithms/examples/07-functional.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct B 5 | { 6 | int b; 7 | void test(int a) 8 | { 9 | b = 3; 10 | std::println("{}, {}", a, b); 11 | } 12 | }; 13 | 14 | int main() 15 | { 16 | B b{ 1 }; 17 | std::function testPtr1 = &B::test; // Okay 18 | testPtr1(&b, 2); 19 | std::println("{}", b.b); 20 | 21 | b.b = 1; 22 | std::function testPtr2 = &B::test; // Okay 23 | testPtr2(b, 3); 24 | std::println("{}", b.b); 25 | 26 | b.b = 1; 27 | std::function testPtr3 = &B::test; // Still okay 28 | testPtr3(b, 4); // not change the original b. 29 | std::println("{}", b.b); 30 | 31 | return 0; 32 | } -------------------------------------------------------------------------------- /04-Ranges & Algorithms/现代C++基础 - 容器,ranges与算法 - Part2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/04-Ranges & Algorithms/现代C++基础 - 容器,ranges与算法 - Part2.pdf -------------------------------------------------------------------------------- /05-Lifetime & Type Safety/README.md: -------------------------------------------------------------------------------- 1 | # Assignment 2 | 3 | ### 前半部分 4 | 5 | 1. 我们先来看一个生命周期UB造成的问题,以说明生命周期的重要性,防止大家说我危言耸听... 6 | 7 | ```c++ 8 | #include 9 | #include 10 | 11 | void SetValue(void* dst) 12 | { 13 | int* m = (int*)dst; 14 | for(int i = 0; i < 4; i++) 15 | m[i] = i; 16 | return; 17 | } 18 | 19 | int main() 20 | { 21 | assert(sizeof(int) == sizeof(float) && alignof(int) == alignof(float)); 22 | 23 | float a[4]{}; 24 | SetValue(a); 25 | for(int i = 0; i < 4; i++) 26 | std::cout << a[i] << ' '; 27 | return 0; 28 | } 29 | ``` 30 | 31 | 在gcc(善用compiler explorer)上用`-O0`和`-O2`分别尝试,看看输出的结果是否一样。 32 | 33 | > 理论上的原因就是`a`里面有四个在生命周期中的`float`,用`int*`去访问是不对的,UB使得gcc随意优化。 34 | 35 | 2. 下面的函数是否有违反生命周期的情况? 36 | 37 | ```c++ 38 | const std::vector& SomeFunc(const std::vector& vec) 39 | { 40 | return vec; 41 | } 42 | 43 | const auto& a = SomeFunc({1,2,3}); 44 | std::cout << a[0]; 45 | ``` 46 | 47 | 这道题需要比较灵活地运用临时变量和引用的生命周期的关系分析才能得出正确结论。 48 | 49 | 看一道类似的题,来自[Stack Overflow - Lambda passed by reference runs when invoked in the constructor, but not when later stored in a data member](https://stackoverflow.com/questions/76815744/lambda-passed-by-reference-runs-when-invoked-in-the-constructor-but-not-when-la): 50 | 51 | ```c++ 52 | #include 53 | #include 54 | 55 | class LambdaStore 56 | { 57 | public: 58 | LambdaStore(const std::function& init_fn) 59 | : fn{init_fn} 60 | { 61 | fn(11.1f); // works, why? 62 | } 63 | 64 | // crashes, why? 65 | void ExecuteStoredLambda() { fn(99.9f); } 66 | 67 | private: 68 | const std::function& fn; 69 | }; 70 | 71 | int main() 72 | { 73 | LambdaStore lambdaStore([](float a) { std::cout << a << '\n'; }); 74 | lambdaStore.ExecuteStoredLambda(); 75 | return 0; 76 | } 77 | ``` 78 | 79 | 这里只需要改变一个字符,就可以让代码无UB,是哪个? 80 | 81 | > 特别说一句(和这题关系不大),无捕获的lambda表达式(stateless lambda)是可以隐式转型成函数指针的,当lambda表达式离开生命周期后也能正常用函数指针访问相应的函数(结合没有捕获来理解,这件事情还算自然)。 82 | 83 | 3. 我们写一个非常简单的`inplace_vector`,练习一下placement new。这是C++26会引入的新容器,它在栈上分配,但与`std::array`不同的是,它只是保留了固定大小的内存,在插入的时候才会真正构造。我们只完成它的析构函数、`.size()`、`operator[]`、`push_back`和`pop_back`。 84 | 85 | ```c++ 86 | template 87 | class InplaceVector 88 | { 89 | public: 90 | InplaceVector() = default; 91 | 92 | std::size_t Size() const; 93 | void PushBack(const T&); 94 | void PopBack(); 95 | // operator[],const和非const两个版本 96 | 97 | ~InplaceVector() { ... } 98 | private: 99 | // 我们假设成员是一个具有合适对齐的buffer,能够放下N个元素。 100 | // 同时需要一个std::size_t来记录它目前的实际大小。 101 | }; 102 | ``` 103 | 104 | 我们要求: 105 | 106 | + 析构函数正确地释放每一个元素。 107 | + `operator[]`、`push_back`和`pop_back`暂时不检查当前的实际大小是否超过`N`。 108 | 109 | > 思考一下,这个buffer和`std::array`内部使用的`T[N]`有什么区别? 110 | 111 | 4. 对下面的变量进行内存重用,哪些合法,哪些非法? 112 | 113 | ```c++ 114 | struct A { int a; const float b; } a1 { 1, 1.0f }; 115 | static const int a2 = 2; 116 | 117 | int main() 118 | { 119 | const int a3 = 2; 120 | const A a4{ 1, 2.0f }; 121 | // 重用a1.a是否合法?a1.b是否合法?a2, a3, a4.a, a4.b呢? 122 | } 123 | ``` 124 | 125 | 5. 下面的代码合法吗? 126 | 127 | ```c++ 128 | std::byte* dataFromNetwork = stream->getBytes(); // 得到一个已经写入内容的byte buffer 129 | if (dataFromNetwork[0] == std::byte{'A'}) 130 | process_foo(reinterpret_cast(dataFromNetwork)); 131 | else 132 | process_bar(reinterpret_cast(dataFromNetwork)); 133 | ``` 134 | 135 | 假设`Foo`和`Bar`都是trivially copyable的。 136 | 137 | ## 后半部分 138 | 139 | 1. 我们用一道题来说明指针的比较不只是内部地址值的比较,还涉及到类型的转化。 140 | 141 | ```c++ 142 | struct A { int a; }; 143 | struct B { int b; }; 144 | struct C : A, B { int ddd; }; 145 | C c; 146 | ``` 147 | 148 | 下面的各个`bool`,哪些可以确定结果(是什么?),哪些不能确定?为什么? 149 | 150 | ```c++ 151 | A* aPtr = &c; 152 | bool test = (aPtr == &c); 153 | bool test2 = (static_cast(aPtr) == &c); 154 | bool test3 = (reinterpret_cast(aPtr) == &c); 155 | 156 | B* bPtr = &c; 157 | bool test4 = (bPtr == &c); 158 | bool test5 = (static_cast(bPtr) == &c); 159 | bool test6 = (reinterpret_cast(bPtr) == &c); 160 | ``` 161 | 162 | 如果我们把`A`和`B`的成员都去掉,只是空类,上面的结果有变化吗? 163 | 164 | > 这个题还是有难度的,不会可以直接看答案。。 165 | 166 | 2. 我们来给Slicing problem一个解决方案。对下面的继承关系: 167 | 168 | ```c++ 169 | class A 170 | { 171 | public: 172 | A(int init_a) : a{init_a}{} 173 | int GetBaseVal() const { return a; } 174 | virtual ~A() = default; 175 | // 编写一个Clone方法 176 | protected: 177 | // TODO... 178 | private: 179 | int a; 180 | }; 181 | 182 | class B : public A 183 | { 184 | public: 185 | B(int init_a, int init_b): A{init_a}, b{init_b} {} 186 | int GetDerivedVal() const { return b; } 187 | // 编写一个Clone方法 188 | private: 189 | int b; 190 | }; 191 | ``` 192 | 193 | 使得下面的代码可以成立/报错: 194 | 195 | ```c++ 196 | void Test(A& a) 197 | { 198 | B b{2,3}; 199 | a = b; // Make it compile error 200 | a.Clone(b); // Right 201 | } 202 | 203 | void Test2(A& a) 204 | { 205 | A a2{4}; 206 | a = a2; // Make it compile error 207 | a.Clone(a2); // Right 208 | } 209 | 210 | A a{1}; 211 | B b{4,5}; 212 | Test(a); 213 | std::println("{}", a.GetBaseVal()); // 2 214 | Test2(a); 215 | std::println("{}", a.GetBaseVal()); // 4 216 | 217 | Test(b); 218 | std::println("{} {}", b.GetBaseVal(), b.GetDerivedVal()); // 2, 3 219 | Test2(b); // 运行时错误(即利用转型的抛异常),因为A不能拷贝到B类型。 220 | ``` 221 | 222 | 3. `const_cast`的一个例子:[c++ - execv() and const-ness - Stack Overflow](https://stackoverflow.com/questions/190184/execv-and-const-ness)。 223 | 224 | 4. `typeid`的例子: 225 | 226 | ```c++ 227 | class A { }; 228 | class B : public A { }; 229 | 230 | void OutputType(A& a) 231 | { 232 | std::println("{}", typeid(a).name()); 233 | } 234 | 235 | B b; 236 | OutputType(b); 237 | ``` 238 | 239 | 看看输出什么;如果给`A`加一个public的虚析构函数,再看看会输出什么。 240 | 241 | 5. 练习一下`std::variant`,对`std::vector>`,把所有的元素拼接为字符串;特别地,如果是`std::string`,则不变;否则用`std::to_string`变为字符串。 242 | 243 | > **特别地,在C++26开始,`visit`成为了`std::variant`的一个成员方法。** 244 | 245 | 6. 对于`std::any`,有一种比较巧妙的使用方法来保留类型的部分信息,从而完成无继承和虚函数的情况下实现不同类型的函数调用。这么说可能有点抽象,我们来看一个例子: 246 | 247 | ```c++ 248 | class AnimalInterface { public: std::string Talk(int); void Walk(); }; 249 | class Dog { 实现上面两个函数; }; 250 | class Cat { 实现上面两个函数; }; 251 | 252 | void Test(const AnimalInterface& animal) 253 | { 254 | animal.Walk(); 255 | std::println("{}", animal.Talk(1)); 256 | } 257 | 258 | int main() 259 | { 260 | Test(Dog{}); 261 | Test(Cat{}); 262 | return 0; 263 | } 264 | ``` 265 | 266 | 和继承的区别在于,如果继承想实现上述功能,就需要`Dog`和`Cat`都继承自`AnimalInterface`,同时`Talk`和`Walk`都是虚的,最后也必须用引用和指针才能指涉子类型。而我们的类的目标在于消除这些问题,**变为非侵入式的设计,即用户只需要实现AnimalInterface规定的接口,就可直接赋给`AnimalInterface`**。 267 | 268 | 既然想达到这么一个目的,我们自然的想法是将获得的object赋给`std::any`进行存储,于是大概长这样: 269 | 270 | ```c++ 271 | class AnimalInterface 272 | { 273 | public: 274 | // 1. 写一个模板构造函数,它接受任意对象,用以构造hiddenObject 275 | private: 276 | std::any hiddenObject_; 277 | }; 278 | ``` 279 | 280 | 随后,我们考虑为它增加`Talk`和`Walk`方法。注意到我们的模板类型只存在于构造函数中,没法在`Talk`和`Walk`中用`any_cast`恢复原来的类型。因此,我们需要在构造函数中就把这个类型以某种方式固定为我们的成员,至少是在函数调用中可以使用的成员。 281 | 282 | 我们先不考虑固定的问题,先考虑在构造函数里如何写这么一个方法。自然可以用lambda表达式: 283 | 284 | ```c++ 285 | // 2. 请在构造函数里写一个lambda表达式,它对hiddenObject做any_cast,变换回原本的类型,再调用Walk。 286 | ``` 287 | 288 | 我们可以注意到,带有capture的lambda表达式是不能作为成员的,因为它的类型不能跑到成员位置去。但是stateless lambda是可以的!通过转型成函数指针,我们就可以固定下来这个lambda。又注意到,我们只对`hiddenObject`做了capture,完全可以把它变换到参数去,从而让lambda无capture。 289 | 290 | ```c++ 291 | // 3. 请给AnimalInterface写两个函数指针成员walkProxy_和talkProxy_,使得它能够正确地保存两个stateless lambda;如果你没明白这个需求,看一下我们后面要求怎样调用。 292 | ``` 293 | 294 | 这样,我们就可以完整的实现`AnimalInterface`的`Walk`和`Talk`: 295 | 296 | ```c++ 297 | void Walk() { walkProxy_(hiddenObject_); } 298 | std::string Talk(int times) { return talkProxy_(hiddenObject_, times); } 299 | ``` 300 | 301 | 来测试一下,对下面的两个类,你的`AnimalInterface`可以正确工作: 302 | 303 | ```c++ 304 | class Dog 305 | { 306 | public: 307 | void Walk() { std::println("Dog walk..."); } 308 | std::string Talk(int times) 309 | { 310 | std::string result{}; 311 | for (int i = 0; i < times; i++) 312 | result += "Woof! "; 313 | return result; 314 | } 315 | }; 316 | 317 | class Cat 318 | { 319 | public: 320 | void Walk() { std::println("Cat walk..."); } 321 | std::string Talk(int times) 322 | { 323 | return "Meow! Lazy to talk for " + std::to_string(times) + " times."; 324 | } 325 | }; 326 | 327 | // 事实上如果我们的Walk和Talk实现为const方法(当然这最好也一并要求Dog和Cat也是const的),这里也可以用const AnimalInterface&。 328 | void Test(AnimalInterface animal) 329 | { 330 | animal.Walk(); 331 | std::println("{}", animal.Talk(1)); 332 | } 333 | 334 | int main() 335 | { 336 | Test(Dog{}); 337 | Test(Cat{}); 338 | return 0; 339 | } 340 | ``` 341 | 342 | 343 | -------------------------------------------------------------------------------- /05-Lifetime & Type Safety/现代C++基础 - 生命周期与类型安全.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/05-Lifetime & Type Safety/现代C++基础 - 生命周期与类型安全.pdf -------------------------------------------------------------------------------- /06-Programming in Multiple Files/Answer.md: -------------------------------------------------------------------------------- 1 | # Answer 2 | 3 | 2. 理论上来说能通过编译,因为在`a.cpp`中调用了`Func`,于是其object file也会生成相应的代码,在链接时就可以使`main.cpp`找到符号。这也称为“隐式实例化”。 4 | 5 | 然而,由于编译器可以进行优化,例如在这里可以把`Func`的函数体完全放入`test`,从而就可以抹除`Func`的符号,因此不能保证通过编译。这就是为什么需要显式实例化。 6 | 7 | 3. 如果`#include "a.tpp"`,则每个源文件都会要实例化一份`Func`,由链接器进行符号的合并;而如果`#include "a.h"`,则每个源文件都只会生成符号,仅在`a.cpp`中进行实例化,在链接时再找到相应的符号,进而缩短编译时间。 8 | 9 | 在C++11开始加入了Explicit instantiation declaration(显式实例化声明),从而可以进行合并: 10 | 11 | ```c++ 12 | // a.h 13 | template 14 | void Func() { std::println("Hello."); } 15 | extern template void Func(); // 显式实例化声明,声明常用的模板实例 16 | 17 | // a.cpp 18 | template void Func(); 19 | ``` 20 | 21 | 当`#include "a.h"`时,如果使用`Func`,则不会进行实例化,而是链接时进行符号查找。但一个实体在以下情况下仍可能进行实例化: 22 | 23 | + 函数内联或者consteval语境中使用,因为它们不能拖延到链接。 24 | + 函数需要自动推断返回值类型,因为需要实例化才能确定这些类型。 25 | + 类模板和模板别名,因为它们不是通过链接确定的。 26 | 27 | 应用这个特性的主要是函数模板(包括类中的模板方法)和我们将来要讲的变量模板。 28 | 29 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/README.md: -------------------------------------------------------------------------------- 1 | # Announcement 2 | 3 | Docker使用: 4 | 5 | ```bash 6 | docker pull extracreativity/linux-cpp-module:first 7 | docker run -it --name 你的容器名字 extracreativity/linux-cpp-module:first 8 | ``` 9 | 10 | 以上拉取了镜像并创建了容器,可以按`Ctrl + Z`来退出容器的命令行。此后如果想停止容器可以用`docker stop 名字`,停止的容器重新启动可以用`docker start 名字`(当然这两个可以通过docker的GUI操作)。如果容器已经开始,想要进入新的命令行,可以使用命令`docker exec -it bash`。 11 | 12 | 更详细的环境使用说明可以看[C++ Modules with Build Tools - zhihu](https://zhuanlan.zhihu.com/p/698401227)。 13 | 14 | # Assignment 15 | 16 | 1. 对你之前写过的某个类做声明与定义的分离,分别写到头文件和源文件里。别忘了Header guard。 17 | 18 | 2. 对于下面的例子: 19 | 20 | ```c++ 21 | // a.h 22 | #pragma once 23 | template void Func(); 24 | 25 | // a.cpp 26 | #include "a.h" 27 | #include 28 | 29 | template 30 | void Func() { std::println("Hello."); } 31 | static void test() { Func(); } 32 | 33 | // main.cpp 34 | #include "a.h" 35 | int main() 36 | { 37 | Func(); 38 | return 0; 39 | } 40 | ``` 41 | 42 | 从理论上来说能够通过编译吗?为什么?实际情况下有没有其他可能? 43 | 44 | 3. 显式实例化有时可以用来减少实例化次数,缩短编译时间。例如: 45 | 46 | ```c++ 47 | // a.tpp 48 | template 49 | void Func() { std::println("Hello."); } 50 | 51 | // a.h 52 | template void Func(); 53 | 54 | // a.cpp 55 | template void Func(); 56 | ``` 57 | 58 | 在若干个源文件中,如果都`#include "a.tpp"`并使用模板会发生什么?如果有的文件只使用了`Func`,可以改成`#include "a.h"`,为什么要这么做? -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/example1/Vector3.cpp: -------------------------------------------------------------------------------- 1 | #include "Vector3.h" 2 | #include 3 | 4 | int Vector3::dim_ = 3; 5 | 6 | Vector3::Vector3(float x, float y, float z) : x_{ x }, y_{ y }, z_{ z } {} 7 | 8 | float Vector3::GetLength() const 9 | { 10 | return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); 11 | } 12 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/example1/Vector3.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | class Vector3 3 | { 4 | public: 5 | Vector3(float x, float y, float z); 6 | float GetLength() const; 7 | static auto GetDim() { return dim_; } 8 | 9 | private: 10 | float x_, y_, z_; 11 | static int dim_; 12 | }; 13 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/example1/func.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Vector3.h" 3 | 4 | void Hello(){ 5 | std::cout << "Hello, world!\n"; 6 | } 7 | 8 | void Hello2(Vector3 vec){ 9 | std::cout << vec.GetLength(); 10 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/example1/func.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | class Vector3; 3 | void Hello(); 4 | void Hello2(Vector3 vec); -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/example1/main.cpp: -------------------------------------------------------------------------------- 1 | #include "namespace.h" 2 | #include "singleton.h" 3 | 4 | int main() 5 | { 6 | Test::A a{1}; 7 | Test::Func(); 8 | [[maybe_unused]] auto b = a.GetX(); 9 | Test2::Test3::B::Output(); // Namespace::Class::StaticMethod(...); 10 | Test6::Func(); 11 | [[maybe_unused]] auto singleton = Singleton::GetInstance(); 12 | return 0; 13 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/example1/namespace.cpp: -------------------------------------------------------------------------------- 1 | #include "namespace.h" 2 | #include 3 | 4 | namespace Test 5 | { 6 | 7 | int A::GetX() const noexcept { return x_; } 8 | 9 | void Func(){ std::cout << "Wow.\n"; } 10 | 11 | } // namespace Test 12 | 13 | namespace Test2 14 | { 15 | namespace Test3 16 | { 17 | 18 | void B::Output() { std::cout << "Output B.\n"; }; 19 | 20 | } // namespace Test2::Test3 21 | } // namespace Test2 22 | 23 | namespace Test4::Test5{ 24 | void C::Output() { std::cout << "Output C.\n"; }; 25 | } 26 | 27 | namespace Test6::Implv1{ 28 | void Func(){ std::cout << "This is v1.\n"; }; 29 | } 30 | 31 | // clang will report warning, but this seems a wrong report. 32 | namespace Test6::Implv2{ 33 | void Func(){ std::cout << "This is v2.\n"; }; 34 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/example1/namespace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | namespace Test 3 | { 4 | 5 | class A 6 | { 7 | public: 8 | A(int x): x_{x}{} 9 | int GetX() const noexcept; 10 | private: 11 | int x_; 12 | }; 13 | 14 | void Func(); 15 | 16 | } // namespace Test 17 | 18 | namespace Test2 19 | { 20 | namespace Test3 21 | { 22 | class B { public: static void Output(); }; 23 | 24 | } // namespace Test2::Test3 25 | } // namespace Test2 26 | 27 | namespace Test4::Test5 // since C++17 28 | { 29 | class C { public: static void Output(); }; 30 | } 31 | 32 | namespace Test6 33 | { 34 | namespace Implv1 35 | { 36 | void Func(); 37 | } 38 | 39 | inline namespace Implv2 40 | { 41 | void Func(); 42 | } 43 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/example1/singleton.cpp: -------------------------------------------------------------------------------- 1 | #include "singleton.h" 2 | 3 | Singleton& Singleton::GetInstance(){ 4 | static Singleton* instance = new Singleton{}; 5 | return *instance; 6 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/example1/singleton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | class Singleton 3 | { 4 | public: 5 | static Singleton& GetInstance(); 6 | private: 7 | Singleton() = default; 8 | }; -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/example1/template.cpp: -------------------------------------------------------------------------------- 1 | #include "template.h" 2 | 3 | // define the template 4 | template 5 | void Func(const T&){ /* ... */ } 6 | 7 | // instantiate explicitly. 8 | template void Func(const int&); -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/example1/template.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // declares the template 3 | template void Func(const T&); 4 | 5 | class A 6 | { 7 | public: 8 | template void Func(T); // declaration 9 | }; 10 | 11 | template 12 | void A::Func(T x){ /* ... */} // definition 13 | 14 | template 15 | class B 16 | { 17 | public: 18 | template void Func(U); 19 | }; 20 | 21 | template 22 | template 23 | void B::Func(U){ /* ... */} 24 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/extern-C-test/test1/a.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "a.h" 3 | 4 | extern "C" int a = 1; 5 | extern "C" void Func() 6 | { 7 | std::cout << "a = " << a << '\n'; 8 | std::cout << "Hello, world!\n"; 9 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/extern-C-test/test1/a.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define EXTERN_C_BEGIN \ 4 | extern "C" \ 5 | { 6 | #define EXTERN_C_END } 7 | 8 | #ifdef __cplusplus 9 | EXTERN_C_BEGIN 10 | #endif 11 | 12 | int a; 13 | void Func(); 14 | 15 | #ifdef __cplusplus 16 | EXTERN_C_END 17 | #endif 18 | 19 | #undef EXTERN_C_BEGIN 20 | #undef EXTERN_C_END 21 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/extern-C-test/test1/main.c: -------------------------------------------------------------------------------- 1 | #include "a.h" 2 | 3 | int main() 4 | { 5 | Func(); 6 | return 0; 7 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/extern-C-test/test2/a.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "a.hpp" 3 | 4 | extern "C" int a = 1; 5 | extern "C" void Func() 6 | { 7 | std::cout << "a = " << a << '\n'; 8 | std::cout << "Hello, world!\n"; 9 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/extern-C-test/test2/a.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | extern int a; 3 | extern void Func(); 4 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/extern-C-test/test2/a.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | class A{}; // C++ features. 3 | 4 | extern "C" int a; 5 | extern "C" void Func(); 6 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/extern-C-test/test2/main.c: -------------------------------------------------------------------------------- 1 | #include "a.h" 2 | 3 | int main() 4 | { 5 | Func(); 6 | return 0; 7 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/extern-C-test/xmake.lua: -------------------------------------------------------------------------------- 1 | target("extern-C-lib1") 2 | set_kind("static") 3 | add_headerfiles("test1/a.h") 4 | add_files("test1/a.cpp") 5 | 6 | target("extern-C-test1") 7 | set_kind("binary") 8 | add_deps("extern-C-lib1") 9 | add_headerfiles("test1/a.h") 10 | add_files("test1/main.c") 11 | 12 | target("extern-C-lib2") 13 | set_kind("static") 14 | add_headerfiles("test2/a.hpp") 15 | add_files("test2/a.cpp") 16 | 17 | target("extern-C-test2") 18 | set_kind("binary") 19 | add_deps("extern-C-lib2") 20 | add_headerfiles("test2/a.h") 21 | add_files("test2/main.c") 22 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/inline-example/func.cpp: -------------------------------------------------------------------------------- 1 | #include "func.h" 2 | #include 3 | // Then func.cpp and main.cpp will both have Hello() and A. 4 | 5 | void Hello2() { std::cout << A::a++ << "\n"; Hello(); } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/inline-example/func.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | inline void Hello() 5 | { 6 | std::cout << "Hello, world!\n"; 7 | } 8 | 9 | void Hello2(); 10 | 11 | struct A{ 12 | static inline int a = 1; 13 | }; -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/inline-example/main.cpp: -------------------------------------------------------------------------------- 1 | #include "func.h" 2 | 3 | int main() 4 | { 5 | Hello(); 6 | Hello2(); 7 | std::cout << A::a << "\n"; 8 | return 0; 9 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/inline-test/DLLMacro.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #if defined _WIN32 || defined __CYGWIN__ 3 | #define DLL_IMPORT __declspec(dllimport) 4 | #define DLL_EXPORT __declspec(dllexport) 5 | #define DLL_LOCAL 6 | #else 7 | #if __GNUC__ >= 4 8 | #define DLL_IMPORT __attribute__ ((visibility ("default"))) 9 | #define DLL_EXPORT __attribute__ ((visibility ("default"))) 10 | #define DLL_LOCAL __attribute__ ((visibility ("hidden"))) 11 | #else 12 | #define DLL_IMPORT 13 | #define DLL_EXPORT 14 | #define DLL_LOCAL 15 | #endif 16 | #endif 17 | 18 | #if defined DLL_MACRO_NEED_IMPORT 19 | #define DLL_PORT DLL_IMPORT 20 | #elif defined DLL_MACRO_NEED_EXPORT 21 | #define DLL_PORT DLL_EXPORT 22 | #else 23 | #define DLL_PORT 24 | #endif 25 | 26 | #undef DLL_MACRO_NEED_IMPORT 27 | #undef DLL_MACRO_NEED_EXPORT 28 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/inline-test/func.cpp: -------------------------------------------------------------------------------- 1 | #include "func.h" 2 | #include "test.h" 3 | #include 4 | 5 | void Func() 6 | { 7 | var++; 8 | std::cout << "Var in library is " << var << "\n"; 9 | std::cout << "What??\n"; 10 | 11 | // strip the comment, and recompile with: 12 | // xmake -b inline-dynamic-lib 13 | // then xmake run inline-dynamic-test (i.e. no recompilation). 14 | 15 | // for static library, xmake -b inline-static-lib 16 | // xmake run inline-static-test is still the old code. 17 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/inline-test/func.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined FUNC_H_IMPORT_ 4 | #define DLL_MACRO_NEED_IMPORT 5 | #elif defined FUNC_H_EXPORT_ 6 | #define DLL_MACRO_NEED_EXPORT 7 | #endif 8 | 9 | #include "DLLMacro.h" 10 | 11 | DLL_PORT void Func(); 12 | class DLL_PORT A {}; 13 | 14 | #undef DLL_PORT -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/inline-test/main.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include "func.h" 3 | #include 4 | 5 | int main() 6 | { 7 | std::cout << "Var in main is " << var << "\n"; 8 | Func(); 9 | std::cout << "Var in main is " << var << "\n"; 10 | return 0; 11 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/inline-test/test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | inline int var = 0; -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/inline-test/xmake.lua: -------------------------------------------------------------------------------- 1 | target("inline-static-lib") 2 | set_kind("static") 3 | add_headerfiles("*.h") 4 | add_files("func.cpp") 5 | 6 | target("inline-static-test") 7 | set_kind("binary") 8 | add_deps("inline-static-lib") 9 | add_headerfiles("*.h") 10 | add_files("main.cpp") 11 | 12 | target("inline-dynamic-lib") 13 | set_kind("shared") 14 | add_files("func.cpp") 15 | add_headerfiles("*.h") 16 | add_defines("FUNC_H_EXPORT_") 17 | 18 | target("inline-dynamic-test") 19 | set_kind("binary") 20 | add_deps("inline-dynamic-lib") 21 | add_headerfiles("*.h") 22 | add_files("main.cpp") 23 | add_defines("FUNC_H_IMPORT_") 24 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/linkage-test/a.cpp: -------------------------------------------------------------------------------- 1 | #include "a.h" 2 | 3 | int a = 1; // defines extern int a in a.h. 4 | // define extern const int b in b.h; extern is necessary. 5 | extern const int b = 10086; 6 | 7 | // internal linkage below. 8 | [[maybe_unused]] static int c = 1; 9 | static void Func(); // declarations 10 | namespace { 11 | 12 | void Func2(); // declarations 13 | 14 | } 15 | 16 | [[maybe_unused]] static void Func(){ 17 | // Cannot be referenced by other TUs. 18 | } 19 | 20 | namespace{ 21 | 22 | [[maybe_unused]] void Func2(){ 23 | // Cannot be referenced by other TUs. 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/linkage-test/a.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | extern int a; 3 | extern const int b; -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/linkage-test/main.cpp: -------------------------------------------------------------------------------- 1 | #include "a.h" 2 | #include 3 | 4 | template void NonStaticFunc(); 5 | template void StaticFunc(); 6 | 7 | int main() 8 | { 9 | NonStaticFunc(); 10 | // StaticFunc(); // Link-time error 11 | std::cout << "a = " << a << ", b = " << b << "\n"; 12 | return 0; 13 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/linkage-test/template.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | void NonStaticFunc() { std::cout << "Non static func.\n"; } 5 | 6 | template 7 | static void StaticFunc() { std::cout << "Static func.\n"; } 8 | 9 | template void NonStaticFunc(); 10 | template void StaticFunc(); -------------------------------------------------------------------------------- /06-Programming in Multiple Files/code/xmake.lua: -------------------------------------------------------------------------------- 1 | set_project("Programming in Multiple Files") 2 | set_xmakever("2.8.7") 3 | set_version("0.0.0") 4 | 5 | add_rules("mode.debug", "mode.release") 6 | add_rules("plugin.compile_commands.autoupdate", {outputdir = ".vscode"}) 7 | set_languages("cxx23") 8 | set_policy("build.warning", true) 9 | set_warnings("all") 10 | set_encodings("utf-8") 11 | 12 | target("example") 13 | set_kind("binary") 14 | add_headerfiles("example1/*.h") 15 | add_files("example1/*.cpp") 16 | 17 | target("inline-example") 18 | set_kind("binary") 19 | add_headerfiles("inline-example/*.h") 20 | add_files("inline-example/*.cpp") 21 | 22 | target("linkage-test") 23 | set_kind("binary") 24 | add_headerfiles("linkage-test/*.h") 25 | add_files("linkage-test/*.cpp") 26 | 27 | includes("inline-test", "extern-C-test") 28 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.29 FATAL_ERROR) 2 | set(ENV{CXXFLAGS} "-stdlib=libc++") 3 | set(CMAKE_CXX_COMPILER /usr/bin/clang++-18) 4 | set(CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS /usr/bin/clang-scan-deps-18) 5 | set(CMAKE_CXX_STANDARD 23) 6 | 7 | # See Help/dev/experimental.rst 8 | set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD 9 | "0e5b6991-d74f-4b3d-a41c-cf096e0b2508") 10 | 11 | project(Test LANGUAGES CXX) 12 | set(CMAKE_CXX_MODULE_STD 1) 13 | 14 | add_subdirectory("module-test") -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 如果需要测试gcc,就直接在这个文件夹下面build,只有两个target能过。 2 | add_executable(module-simple) 3 | target_sources(module-simple 4 | PUBLIC FILE_SET CXX_MODULES 5 | FILES simple/Person.mpp simple/Customer.mpp 6 | PRIVATE simple/main.cpp) 7 | 8 | file(GLOB split_impl_src split-impl/*.cpp) 9 | add_executable(module-split-impl ${split_impl_src}) 10 | target_sources(module-split-impl 11 | PUBLIC FILE_SET CXX_MODULES 12 | FILES split-impl/Person.mpp) 13 | 14 | file(GLOB partition_src partition/*.cpp) 15 | file(GLOB partition_modules partition/*.mpp) 16 | add_executable(module-partition ${partition_src}) 17 | target_sources(module-partition 18 | PUBLIC FILE_SET CXX_MODULES 19 | FILES ${partition_modules}) 20 | 21 | file(GLOB global_frag_src global-fragment/*.cpp) 22 | add_executable(module-global-frag ${global_frag_src}) 23 | target_sources(module-global-frag 24 | PUBLIC FILE_SET CXX_MODULES 25 | FILES global-fragment/Person.mpp 26 | # PUBLIC FILE_SET HEADERS FILES global-fragment/Old.h 27 | ) 28 | 29 | # add_executable(module-private private-fragment/main.cpp) 30 | # target_sources(module-private 31 | # PUBLIC FILE_SET CXX_MODULES 32 | # FILES private-fragment/Person.mpp) 33 | 34 | # Header unit isn't supported yet in cmake, so we don't try it. -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/global-fragment/Old.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Old.h" 3 | 4 | void SomeOldLibFunc(std::uint32_t id) 5 | { 6 | std::cout << "In header unit, id = " << id << '\n'; 7 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/global-fragment/Old.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef NEED_PARAM 5 | void SomeOldLibFunc(std::uint32_t); 6 | #else 7 | void SomeOldLibFunc(std::uint32_t = 1); 8 | #endif -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/global-fragment/Person.cpp: -------------------------------------------------------------------------------- 1 | module; // global module fragment 2 | 3 | #define NEED_PARAM 4 | #include "Old.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | module Person; // denote that it's a module implementation 11 | 12 | Person::Person(const std::string& init_name, std::uint64_t init_id): 13 | name{init_name}, id{init_id} {} 14 | 15 | void Person::Print() const 16 | { 17 | std::cout << std::format("Person #{}: {}\n", id, name); 18 | } 19 | 20 | void InnerMethod(); 21 | 22 | // No need to specify any export; it's the duty of module interface! 23 | void HelloWorld() 24 | { 25 | SomeOldLibFunc(1); 26 | std::cout << "Hello, world!\n"; 27 | std::cout << "Calling non-exported method...\n"; 28 | InnerMethod(); 29 | } 30 | 31 | void InnerMethod() 32 | { 33 | std::cout << "No export, not visible from other modules.\n"; 34 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/global-fragment/Person.mpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | #include 5 | 6 | export module Person; // denote that it's a module interface. 7 | 8 | export class Person 9 | { 10 | public: 11 | Person(const std::string& init_name, std::uint64_t init_id); 12 | void Print() const; 13 | 14 | private: 15 | std::string name; 16 | std::uint64_t id; 17 | }; 18 | 19 | export void HelloWorld(); 20 | 21 | // Similar to header files, template definition should still be in 22 | // interface file, otherwise link error. 23 | export template 24 | void TemplateMethod() 25 | { 26 | std::cout << "This is in template method.\n"; 27 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/global-fragment/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | import Person; 3 | 4 | int main() 5 | { 6 | std::cout << "---------module-header-unit---------\n"; 7 | HelloWorld(); 8 | TemplateMethod(); 9 | // InnerMethod(); // error: cannot find identifier (because it's not exported) 10 | Person person{"Haoyang", 6}; 11 | person.Print(); 12 | return 0; 13 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/header-unit/Old.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Old.h" 3 | 4 | void SomeOldLibFunc(std::uint32_t id) 5 | { 6 | std::cout << "In header unit, id = " << id << '\n'; 7 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/header-unit/Old.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | void SomeOldLibFunc(std::uint32_t); -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/header-unit/Person.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | #include 5 | 6 | module Person; // denote that it's a module implementation 7 | import "Old.h"; 8 | 9 | Person::Person(const std::string& init_name, std::uint64_t init_id): 10 | name{init_name}, id{init_id} {} 11 | 12 | void Person::Print() const 13 | { 14 | std::cout << std::format("Person #{}: {}\n", id, name); 15 | } 16 | 17 | void InnerMethod(); 18 | 19 | // No need to specify any export; it's the duty of module interface! 20 | void HelloWorld() 21 | { 22 | SomeOldLibFunc(1); 23 | std::cout << "Hello, world!\n"; 24 | std::cout << "Calling non-exported method...\n"; 25 | InnerMethod(); 26 | } 27 | 28 | void InnerMethod() 29 | { 30 | std::cout << "No export, not visible from other modules.\n"; 31 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/header-unit/Person.mpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | #include 5 | export module Person; // denote that it's a module interface. 6 | 7 | export class Person 8 | { 9 | public: 10 | Person(const std::string& init_name, std::uint64_t init_id); 11 | void Print() const; 12 | 13 | private: 14 | std::string name; 15 | std::uint64_t id; 16 | }; 17 | 18 | export void HelloWorld(); 19 | 20 | // Similar to header files, template definition should still be in 21 | // interface file, otherwise link error. 22 | export template 23 | void TemplateMethod() 24 | { 25 | std::cout << "This is in template method.\n"; 26 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/header-unit/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | import Person; 3 | 4 | int main() 5 | { 6 | std::cout << "---------module-header-unit---------\n"; 7 | HelloWorld(); 8 | TemplateMethod(); 9 | // InnerMethod(); // error: cannot find identifier (because it's not exported) 10 | Person person{"Haoyang", 6}; 11 | person.Print(); 12 | return 0; 13 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/partition/Person-Order.mpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | #include 5 | module Person:Order; 6 | 7 | struct Order 8 | { 9 | int count; 10 | std::string name; 11 | double price; 12 | 13 | Order(int c, const std::string& n, double p) 14 | : count{c}, name{n}, price{p} { } 15 | void Print() const { 16 | std::cout << std::format("count = {}, name = {}, price = {}\n", count, name, price); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/partition/Person-Utils.mpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module Person:Utils; 4 | 5 | import :Order; 6 | 7 | export void PrintOrder(){ 8 | std::cout << "\t, with order\n\t"; 9 | Order{1, "apple", 7.3}.Print(); 10 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/partition/Person.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | #include 5 | #include 6 | module Person; // denote that it's a module implementation 7 | 8 | Person::Person(const std::string& init_name, std::uint64_t init_id): 9 | name{init_name}, id{init_id} {} 10 | 11 | void Person::Print() const 12 | { 13 | std::cout << std::format("Person #{}: {}\n", id, name); 14 | } 15 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/partition/Person.mpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | export module Person; // denote that it's a module interface. 5 | 6 | export import :Utils; 7 | 8 | export class Person 9 | { 10 | public: 11 | Person(const std::string& init_name, std::uint64_t init_id); 12 | void Print() const; 13 | 14 | private: 15 | std::string name; 16 | std::uint64_t id; 17 | }; -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/partition/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | import Person; 3 | 4 | int main() 5 | { 6 | std::cout << "---------module-partition---------\n"; 7 | Person person{"Haoyang", 6}; 8 | person.Print(); 9 | PrintOrder(); 10 | return 0; 11 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/private-fragment/Person.mpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | #include 5 | #include 6 | export module Person; // denote that it's a module interface. 7 | 8 | export class Person; 9 | export Person* CreatePerson(); 10 | export void PrintPerson(Person*); 11 | export void DestroyPerson(Person*); 12 | 13 | module:private; 14 | 15 | class Person 16 | { 17 | public: 18 | Person(const std::string& init_name, std::uint64_t init_id): 19 | name{init_name}, id{init_id} {} 20 | 21 | void Print() const { 22 | std::cout << std::format("Person #{}: {}\n", id, name); 23 | } 24 | 25 | private: 26 | std::string name; 27 | std::uint64_t id; 28 | }; 29 | 30 | Person* CreatePerson() { return new Person{"Sheng", 6}; } 31 | void PrintPerson(Person* p) { p->Print(); } 32 | void DestroyPerson(Person* p) { delete p; } 33 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/private-fragment/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | import Person; 3 | 4 | int main() 5 | { 6 | std::cout << "---------module-private---------\n"; 7 | auto p = CreatePerson(); 8 | // p->Print(); // error: use undefined type "Person" 9 | PrintPerson(p); 10 | DestroyPerson(p); 11 | return 0; 12 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/simple/Customer.mpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | #include 5 | 6 | export module Customer; 7 | export import Person; // users can also get access to Person. 8 | 9 | export class Customer : public Person 10 | { 11 | public: 12 | Customer(const std::string& init_name, std::uint64_t init_id): 13 | Person{ "customer " + init_name, init_id} {} 14 | 15 | void Print() const { 16 | std::cout << "This is a customer...\n"; 17 | Person::Print(); 18 | } 19 | }; -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/simple/Person.mpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | export module Person; // denote that it's a module interface. 8 | 9 | export class Person 10 | { 11 | public: 12 | Person(const std::string& init_name, std::uint64_t init_id): 13 | name{init_name}, id{init_id} {} 14 | 15 | void Print() const { 16 | std::cout << std::format("Person #{}: {}\n", id, name); 17 | } 18 | 19 | private: 20 | std::string name; 21 | std::uint64_t id; 22 | }; 23 | 24 | // You still need to declare before use, if it's impossible to define before use. 25 | // Here you can just put the definition, we're just showing an example. 26 | void InnerMethod(); 27 | 28 | // You can write definition in module interface; it obeys ODR! 29 | export void HelloWorld() 30 | { 31 | std::cout << "Hello, world!\n"; 32 | std::cout << "Calling non-exported method...\n"; 33 | InnerMethod(); 34 | } 35 | 36 | void InnerMethod() 37 | { 38 | std::cout << "No export, not visible from other modules.\n"; 39 | } 40 | 41 | export template 42 | void TemplateMethod() 43 | { 44 | std::cout << "This is in template method.\n"; 45 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/simple/main.cpp: -------------------------------------------------------------------------------- 1 | #include // This is not standard, but we don't want main to be a module. 2 | // So this clang dialect is adopted. 3 | import Customer; // Customer has "export import Person", 4 | // so all exported things of Person can be used. 5 | 6 | int main() 7 | { 8 | std::cout << "---------module-simple---------\n"; 9 | HelloWorld(); 10 | TemplateMethod(); 11 | // InnerMethod(); // error: cannot find identifier (because it's not exported) 12 | 13 | Person person{"Haoyang", 6}; 14 | person.Print(); 15 | Customer customer{ "lrzzzzzzzzzz", 666 }; 16 | customer.Print(); 17 | return 0; 18 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/split-impl/Person-Funcs.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | module Person; // denote that it's a module implementation 5 | 6 | void InnerMethod(); 7 | 8 | // No need to specify any export; it's the duty of module interface! 9 | void HelloWorld() 10 | { 11 | std::cout << "Hello, world!\n"; 12 | std::cout << "Calling non-exported method...\n"; 13 | InnerMethod(); 14 | } 15 | 16 | void InnerMethod() 17 | { 18 | std::cout << "No export, not visible from other modules.\n"; 19 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/split-impl/Person.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | #include 5 | #include 6 | module Person; // denote that it's a module implementation 7 | 8 | Person::Person(const std::string& init_name, std::uint64_t init_id): 9 | name{init_name}, id{init_id} {} 10 | 11 | void Person::Print() const 12 | { 13 | std::cout << std::format("Person #{}: {}\n", id, name); 14 | } 15 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/split-impl/Person.mpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | #include 5 | 6 | export module Person; // denote that it's a module interface. 7 | 8 | export class Person 9 | { 10 | public: 11 | Person(const std::string& init_name, std::uint64_t init_id); 12 | void Print() const; 13 | 14 | private: 15 | std::string name; 16 | std::uint64_t id; 17 | }; 18 | 19 | export void HelloWorld(); 20 | 21 | // Similar to header files, template definition should still be in 22 | // interface file, otherwise link error. 23 | export template 24 | void TemplateMethod() 25 | { 26 | std::cout << "This is in template method.\n"; 27 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/split-impl/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | import Person; 3 | 4 | int main() 5 | { 6 | std::cout << "---------module-split-impl---------\\n"; 7 | HelloWorld(); 8 | TemplateMethod(); 9 | // InnerMethod(); // error: cannot find identifier (because it's not exported) 10 | Person person{"Haoyang", 6}; 11 | person.Print(); 12 | return 0; 13 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test-20/xmake.lua: -------------------------------------------------------------------------------- 1 | set_group("module-example-20") 2 | 3 | target("module-simple") 4 | set_kind("binary") 5 | -- when no .mpp exists, you need this additional line. 6 | -- set_policy("build.c++.modules", true) 7 | add_files("simple/main.cpp") 8 | add_files("simple/Person.mpp", "simple/Customer.mpp") 9 | 10 | target("module-split-impl") 11 | set_kind("binary") 12 | add_files("split-impl/*.cpp") 13 | add_files("split-impl/Person.mpp") 14 | 15 | -- target("module-header-unit") 16 | -- set_kind("binary") 17 | -- add_headerfiles("header-unit/*.h") 18 | -- add_files("header-unit/*.cpp") 19 | -- add_files("header-unit/Person.mpp") 20 | 21 | target("module-global-fragment") 22 | set_kind("binary") 23 | add_headerfiles("global-fragment/*.h") 24 | add_files("global-fragment/*.cpp") 25 | add_files("global-fragment/Person.mpp") 26 | 27 | target("module-partition") 28 | set_kind("binary") 29 | add_files("partition/*.cpp") 30 | add_files("partition/*.mpp") 31 | 32 | target("module-private-fragment") 33 | set_kind("binary") 34 | add_files("private-fragment/main.cpp") 35 | add_files("private-fragment/Person.mpp") 36 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(module-simple simple/main.cpp) 2 | target_sources(module-simple 3 | PUBLIC FILE_SET CXX_MODULES 4 | FILES simple/Person.mpp simple/Customer.mpp) 5 | target_compile_features(module-simple PRIVATE cxx_std_23) 6 | 7 | file(GLOB split_impl_src split-impl/*.cpp) 8 | add_executable(module-split-impl ${split_impl_src}) 9 | target_sources(module-split-impl 10 | PUBLIC FILE_SET CXX_MODULES 11 | FILES split-impl/Person.mpp) 12 | target_compile_features(module-simple PRIVATE cxx_std_23) 13 | 14 | file(GLOB partition_src partition/*.cpp) 15 | file(GLOB partition_modules partition/*.mpp) 16 | add_executable(module-partition ${partition_src}) 17 | target_sources(module-partition 18 | PUBLIC FILE_SET CXX_MODULES 19 | FILES ${partition_modules}) 20 | target_compile_features(module-partition PRIVATE cxx_std_23) 21 | 22 | file(GLOB global_frag_src global-fragment/*.cpp) 23 | add_executable(module-global-frag ${global_frag_src}) 24 | target_sources(module-global-frag 25 | PUBLIC FILE_SET CXX_MODULES 26 | FILES global-fragment/Person.mpp 27 | # PUBLIC FILE_SET HEADERS FILES global-fragment/Old.h 28 | ) 29 | target_compile_features(module-global-frag PRIVATE cxx_std_23) 30 | 31 | add_executable(module-private private-fragment/main.cpp) 32 | target_sources(module-private 33 | PUBLIC FILE_SET CXX_MODULES 34 | FILES private-fragment/Person.mpp) 35 | target_compile_features(module-private PRIVATE cxx_std_23) 36 | # Header unit isn't supported yet in cmake, so we don't try it. -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/global-fragment/Old.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Old.h" 3 | 4 | void SomeOldLibFunc(std::uint32_t id) 5 | { 6 | std::cout << "In header unit, id = " << id << '\n'; 7 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/global-fragment/Old.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef NEED_PARAM 5 | void SomeOldLibFunc(std::uint32_t); 6 | #else 7 | void SomeOldLibFunc(std::uint32_t = 1); 8 | #endif -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/global-fragment/Person.cpp: -------------------------------------------------------------------------------- 1 | module; // global module fragment 2 | 3 | #define NEED_PARAM 4 | #include "Old.h" 5 | 6 | module Person; // denote that it's a module implementation 7 | import std; 8 | 9 | Person::Person(const std::string& init_name, std::uint64_t init_id): 10 | name{init_name}, id{init_id} {} 11 | 12 | void Person::Print() const 13 | { 14 | std::println("Person #{}: {}", id, name); 15 | } 16 | 17 | void InnerMethod(); 18 | 19 | // No need to specify any export; it's the duty of module interface! 20 | void HelloWorld() 21 | { 22 | SomeOldLibFunc(1); 23 | std::println("Hello, world!"); 24 | std::println("Calling non-exported method..."); 25 | InnerMethod(); 26 | } 27 | 28 | void InnerMethod() 29 | { 30 | std::println("No export, not visible from other modules."); 31 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/global-fragment/Person.mpp: -------------------------------------------------------------------------------- 1 | export module Person; // denote that it's a module interface. 2 | 3 | import std; 4 | 5 | export class Person 6 | { 7 | public: 8 | Person(const std::string& init_name, std::uint64_t init_id); 9 | void Print() const; 10 | 11 | private: 12 | std::string name; 13 | std::uint64_t id; 14 | }; 15 | 16 | export void HelloWorld(); 17 | 18 | // Similar to header files, template definition should still be in 19 | // interface file, otherwise link error. 20 | export template 21 | void TemplateMethod() 22 | { 23 | std::println("This is in template method."); 24 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/global-fragment/README.md: -------------------------------------------------------------------------------- 1 | 就是在Person.cpp中新增了module;这个global fragment,从而可以用宏控制其行为。 -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/global-fragment/main.cpp: -------------------------------------------------------------------------------- 1 | import std; 2 | import Person; 3 | 4 | int main() 5 | { 6 | std::println("---------module-header-unit---------"); 7 | HelloWorld(); 8 | TemplateMethod(); 9 | // InnerMethod(); // error: cannot find identifier (because it's not exported) 10 | Person person{"Haoyang", 6}; 11 | person.Print(); 12 | return 0; 13 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/header-unit/Old.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Old.h" 3 | 4 | void SomeOldLibFunc(std::uint32_t id) 5 | { 6 | std::cout << "In header unit, id = " << id << '\n'; 7 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/header-unit/Old.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | void SomeOldLibFunc(std::uint32_t); -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/header-unit/Person.cpp: -------------------------------------------------------------------------------- 1 | module Person; // denote that it's a module implementation 2 | import std; 3 | import "Old.h"; 4 | 5 | Person::Person(const std::string& init_name, std::uint64_t init_id): 6 | name{init_name}, id{init_id} {} 7 | 8 | void Person::Print() const 9 | { 10 | std::println("Person #{}: {}", id, name); 11 | } 12 | 13 | void InnerMethod(); 14 | 15 | // No need to specify any export; it's the duty of module interface! 16 | void HelloWorld() 17 | { 18 | SomeOldLibFunc(1); 19 | std::println("Hello, world!"); 20 | std::println("Calling non-exported method..."); 21 | InnerMethod(); 22 | } 23 | 24 | void InnerMethod() 25 | { 26 | std::println("No export, not visible from other modules."); 27 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/header-unit/Person.mpp: -------------------------------------------------------------------------------- 1 | export module Person; // denote that it's a module interface. 2 | 3 | import std; 4 | 5 | export class Person 6 | { 7 | public: 8 | Person(const std::string& init_name, std::uint64_t init_id); 9 | void Print() const; 10 | 11 | private: 12 | std::string name; 13 | std::uint64_t id; 14 | }; 15 | 16 | export void HelloWorld(); 17 | 18 | // Similar to header files, template definition should still be in 19 | // interface file, otherwise link error. 20 | export template 21 | void TemplateMethod() 22 | { 23 | std::println("This is in template method."); 24 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/header-unit/README.md: -------------------------------------------------------------------------------- 1 | 就是在Person.cpp中新增了import "Old.h"。在Visual Studio编译时,你可能需要把“扫描源以查找模块依赖关系”设置为“是”来成功编译。 -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/header-unit/main.cpp: -------------------------------------------------------------------------------- 1 | import std; 2 | import Person; 3 | 4 | int main() 5 | { 6 | std::println("---------module-header-unit---------"); 7 | HelloWorld(); 8 | TemplateMethod(); 9 | // InnerMethod(); // error: cannot find identifier (because it's not exported) 10 | Person person{"Haoyang", 6}; 11 | person.Print(); 12 | return 0; 13 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/partition/Person-Order.mpp: -------------------------------------------------------------------------------- 1 | module Person:Order; 2 | import std; 3 | 4 | struct Order 5 | { 6 | int count; 7 | std::string name; 8 | double price; 9 | 10 | Order(int c, const std::string& n, double p) 11 | : count{c}, name{n}, price{p} { } 12 | void Print() const { 13 | std::println("count = {}, name = {}, price = {}", count, name, price); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/partition/Person-Utils.mpp: -------------------------------------------------------------------------------- 1 | export module Person:Utils; 2 | 3 | import :Order; 4 | import std; 5 | 6 | export void PrintOrder() 7 | { 8 | std::print("\t, with order\n\t"); 9 | Order{ 1, "apple", 7.3 }.Print(); 10 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/partition/Person.cpp: -------------------------------------------------------------------------------- 1 | module Person; // denote that it's a module implementation 2 | import std; 3 | 4 | Person::Person(const std::string& init_name, std::uint64_t init_id): 5 | name{init_name}, id{init_id} {} 6 | 7 | void Person::Print() const 8 | { 9 | std::println("Person #{}: {}", id, name); 10 | } 11 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/partition/Person.mpp: -------------------------------------------------------------------------------- 1 | export module Person; // denote that it's a module interface. 2 | 3 | export import :Utils; 4 | import std; 5 | 6 | export class Person 7 | { 8 | public: 9 | Person(const std::string& init_name, std::uint64_t init_id); 10 | void Print() const; 11 | 12 | private: 13 | std::string name; 14 | std::uint64_t id; 15 | }; -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/partition/README.md: -------------------------------------------------------------------------------- 1 | 这一部分是partition的例子,partition可以是interface的,也可以是implementation的。不过注意,interface的partition和primary interface有明显不同的地方: 2 | + 其他模块可以直接import primary interface,但不能import该interface的特定partition。只有当primary interface对interface partition进行export import时,它所export的东西才实际对外可见。 3 | + interface partition和implementation partition不应同名(即`export module A:B`不能与`module A:B`同时出现,后者也不是前者的实现文件;如果想给partition进行实现的分离,应该使用新的名字,例如`module A:B.impl`,随后在`A:B`中`import :B.impl`)。而primary interface可以有很多implementation,它们以相同的module为名字。 4 | 5 | 在Visual Studio编译时,你可能需要把“扫描源以查找模块依赖关系”设置为“是”来成功编译。不过目前VS的Intellisense还有bug,所以会有一些“错误的报错”,实际上是可以编译的。不是特别确定是否符合标准。 -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/partition/main.cpp: -------------------------------------------------------------------------------- 1 | import std; 2 | import Person; 3 | 4 | int main() 5 | { 6 | std::println("---------module-partition---------"); 7 | Person person{"Haoyang", 6}; 8 | person.Print(); 9 | PrintOrder(); 10 | return 0; 11 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/private-fragment/Person.mpp: -------------------------------------------------------------------------------- 1 | export module Person; // denote that it's a module interface. 2 | 3 | import std; 4 | 5 | export class Person; 6 | export Person* CreatePerson(); 7 | export void PrintPerson(Person*); 8 | export void DestroyPerson(Person*); 9 | 10 | module:private; 11 | 12 | class Person 13 | { 14 | public: 15 | Person(const std::string& init_name, std::uint64_t init_id): 16 | name{init_name}, id{init_id} {} 17 | 18 | void Print() const { 19 | std::println("Person #{}: {}", id, name); 20 | } 21 | 22 | private: 23 | std::string name; 24 | std::uint64_t id; 25 | }; 26 | 27 | Person* CreatePerson() { return new Person{"Sheng", 6}; } 28 | void PrintPerson(Person* p) { p->Print(); } 29 | void DestroyPerson(Person* p) { delete p; } 30 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/private-fragment/README.md: -------------------------------------------------------------------------------- 1 | 演示了private fragment的用法;特别注意,如果一个module定义了private fragment,那么它只能有一个primary interface作为unit,不能再有其他的unit(包括partition或者implementation)。 2 | 3 | 我个人认为在以上限制下其作用十分有限,除非你甚至不想暴露一个类的public方法;gcc和clang也没实现,了解就行了。 -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/private-fragment/main.cpp: -------------------------------------------------------------------------------- 1 | import std; 2 | import Person; 3 | 4 | int main() 5 | { 6 | std::println("---------module-private---------"); 7 | auto p = CreatePerson(); 8 | // p->Print(); // error: use undefined type "Person" 9 | PrintPerson(p); 10 | DestroyPerson(p); 11 | return 0; 12 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/simple/Customer.mpp: -------------------------------------------------------------------------------- 1 | export module Customer; 2 | export import Person; // users can also get access to Person. 3 | import std; 4 | 5 | export class Customer : public Person 6 | { 7 | public: 8 | Customer(const std::string& init_name, std::uint64_t init_id): 9 | Person{ "customer " + init_name, init_id} {} 10 | 11 | void Print() const { 12 | std::println("This is a customer..."); 13 | Person::Print(); 14 | } 15 | }; -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/simple/Person.mpp: -------------------------------------------------------------------------------- 1 | export module Person; // denote that it's a module interface. 2 | 3 | import std; 4 | 5 | export class Person 6 | { 7 | public: 8 | Person(const std::string& init_name, std::uint64_t init_id): 9 | name{init_name}, id{init_id} {} 10 | 11 | void Print() const { 12 | std::println("Person #{}: {}", id, name); 13 | } 14 | 15 | private: 16 | std::string name; 17 | std::uint64_t id; 18 | }; 19 | 20 | // You still need to declare before use, if it's impossible to define before use. 21 | // Here you can just put the definition, we're just showing an example. 22 | void InnerMethod(); 23 | 24 | // You can write definition in module interface; it obeys ODR! 25 | export void HelloWorld() 26 | { 27 | std::println("Hello, world!"); 28 | std::println("Calling non-exported method..."); 29 | InnerMethod(); 30 | } 31 | 32 | void InnerMethod() 33 | { 34 | std::println("No export, not visible from other modules."); 35 | } 36 | 37 | export template 38 | void TemplateMethod() 39 | { 40 | std::println("This is in template method."); 41 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/simple/main.cpp: -------------------------------------------------------------------------------- 1 | import std; 2 | import Customer; // Customer has "export import Person", 3 | // so all exported things of Person can be used. 4 | 5 | int main() 6 | { 7 | std::println("---------module-simple---------"); 8 | HelloWorld(); 9 | TemplateMethod(); 10 | // InnerMethod(); // error: cannot find identifier (because it's not exported) 11 | 12 | Person person{"Haoyang", 6}; 13 | person.Print(); 14 | Customer customer{ "lrzzzzzzzzzz", 666 }; 15 | customer.Print(); 16 | return 0; 17 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/split-impl/Person-Funcs.cpp: -------------------------------------------------------------------------------- 1 | module Person; // denote that it's a module implementation 2 | import std; 3 | 4 | void InnerMethod(); 5 | 6 | // No need to specify any export; it's the duty of module interface! 7 | void HelloWorld() 8 | { 9 | std::println("Hello, world!"); 10 | std::println("Calling non-exported method..."); 11 | InnerMethod(); 12 | } 13 | 14 | void InnerMethod() 15 | { 16 | std::println("No export, not visible from other modules."); 17 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/split-impl/Person.cpp: -------------------------------------------------------------------------------- 1 | module Person; // denote that it's a module implementation 2 | import std; 3 | 4 | Person::Person(const std::string& init_name, std::uint64_t init_id): 5 | name{init_name}, id{init_id} {} 6 | 7 | void Person::Print() const 8 | { 9 | std::println("Person #{}: {}", id, name); 10 | } 11 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/split-impl/Person.mpp: -------------------------------------------------------------------------------- 1 | export module Person; // denote that it's a module interface. 2 | 3 | import std; 4 | 5 | export class Person 6 | { 7 | public: 8 | Person(const std::string& init_name, std::uint64_t init_id); 9 | void Print() const; 10 | 11 | private: 12 | std::string name; 13 | std::uint64_t id; 14 | }; 15 | 16 | export void HelloWorld(); 17 | 18 | // Similar to header files, template definition should still be in 19 | // interface file, otherwise link error. 20 | export template 21 | void TemplateMethod() 22 | { 23 | std::println("This is in template method."); 24 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/split-impl/README.md: -------------------------------------------------------------------------------- 1 | 我们把module interface中的实现分离到module implementation中,module interface只有一个(用export module的开头表示),而module implementation可以有任意多个(用module开头表示,无export)。 2 | 3 | 具体地,即`Person.mpp`是module interface,其他两个是module implementation。 4 | 5 | 我们这里只是一个例子,这种文件长度用一个文件作为module implementation就够了。 -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/split-impl/main.cpp: -------------------------------------------------------------------------------- 1 | import std; 2 | import Person; 3 | 4 | int main() 5 | { 6 | std::println("---------module-split-impl---------"); 7 | HelloWorld(); 8 | TemplateMethod(); 9 | // InnerMethod(); // error: cannot find identifier (because it's not exported) 10 | Person person{"Haoyang", 6}; 11 | person.Print(); 12 | return 0; 13 | } -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/module-test/xmake.lua: -------------------------------------------------------------------------------- 1 | set_group("module-example") 2 | 3 | target("module-simple") 4 | set_kind("binary") 5 | -- when no .mpp exists, you need this additional line. 6 | -- set_policy("build.c++.modules", true) 7 | add_files("simple/main.cpp") 8 | add_files("simple/Person.mpp", "simple/Customer.mpp") 9 | 10 | target("module-split-impl") 11 | set_kind("binary") 12 | add_files("split-impl/*.cpp") 13 | add_files("split-impl/Person.mpp") 14 | 15 | target("module-header-unit") 16 | set_kind("binary") 17 | add_headerfiles("header-unit/*.h") 18 | add_files("header-unit/*.cpp") 19 | add_files("header-unit/Person.mpp") 20 | 21 | target("module-global-fragment") 22 | set_kind("binary") 23 | add_headerfiles("global-fragment/*.h") 24 | add_files("global-fragment/*.cpp") 25 | add_files("global-fragment/Person.mpp") 26 | 27 | target("module-partition") 28 | set_kind("binary") 29 | add_files("partition/*.cpp") 30 | add_files("partition/*.mpp") 31 | 32 | target("module-private-fragment") 33 | set_kind("binary") 34 | add_files("private-fragment/main.cpp") 35 | add_files("private-fragment/Person.mpp") 36 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/modules/xmake.lua: -------------------------------------------------------------------------------- 1 | set_project("Modules test") 2 | set_xmakever("2.8.7") 3 | set_version("0.0.0") 4 | 5 | add_rules("mode.debug", "mode.release") 6 | add_rules("plugin.compile_commands.autoupdate", {outputdir = ".vscode"}) 7 | set_languages("cxx23") 8 | set_policy("build.warning", true) 9 | set_warnings("all") 10 | set_encodings("utf-8") 11 | 12 | set_toolchains("clang-18") 13 | set_runtimes("c++_shared") 14 | includes("module-test-20") 15 | -- includes("module-test") -- 需要测试import std就用这个,否则用上面这个 16 | -------------------------------------------------------------------------------- /06-Programming in Multiple Files/现代C++基础 - 多文件编程.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/06-Programming in Multiple Files/现代C++基础 - 多文件编程.pdf -------------------------------------------------------------------------------- /07-Error Handling/Answer.md: -------------------------------------------------------------------------------- 1 | # Answer 2 | 3 | 1. 全部都会拷贝,不能直接使用引用类型作为模板参数。若使用`std::reference_wrapper`,则代码要改动为: 4 | 5 | ```c++ 6 | void Test( 7 | std::optional>> optVectorParam) 8 | { 9 | if (optVectorParam) 10 | { 11 | auto &optVector = optVectorParam->get(); 12 | if (!optVector.empty()) 13 | std::println("{}", optVector[0]); 14 | } 15 | return; 16 | } 17 | ``` 18 | 19 | 与指针对比一下: 20 | 21 | ```c++ 22 | void Test(std::vector* optVectorPtr) 23 | { 24 | if (optVectorPtr && !optVectorPtr->empty()) // still short-circuit 25 | std::println("{}", optVector[0]); 26 | return; 27 | } 28 | ``` 29 | 30 | 显然迂回的策略十分丑陋;此外,我们说过`referece_wrapper`本质上就是有一个`T*`,`optional`又给它搞了一个`bool`,明明空指针就能表示这种空状态,白白浪费了空间。 31 | 32 | **总结:对于函数参数,如果非空参数本来是要传递引用`T&`,则(在相关提案通过前)传递可空参数不要使用`std::optional`,而是使用`T*`**。 33 | 34 | 在P2988里,`std::optional`与指针是比较类似的: 35 | 36 | ```c++ 37 | void Test(std::optional&> optVectorPtr) 38 | { 39 | if (optVectorPtr && !optVectorPtr->empty()) // still short-circuit 40 | std::println("{}", optVector[0]); 41 | return; 42 | } 43 | ``` 44 | 45 | 它的本质实现也是存储了一个指针;因此,**不能使用临时值来对其进行初始化**: 46 | 47 | ```c++ 48 | Test(std::vector{ 1,2,3 }); // Compile error 49 | ``` 50 | 51 | 因为`optional`不期望进行任何生命周期的管理,所以明确拒绝`const&`这种自动延长临时值生命周期的功能。但是它留了一个空子: 52 | 53 | ```c++ 54 | Test(std::optional{ std::vector{ 1,2,3 } }); // Yes 55 | ``` 56 | 57 | 也就是说可以用`std::optional`构造`std::optional`,此时后者得到的是前者存储的值的地址。**但是这种接受临时值的方式只在函数参数这里有效,因为函数调用的过程中`std:::optional`没有析构,而如果析构了就会导致生命周期上的错误**。例如: 58 | 59 | ```c++ 60 | std::optional Func() { return 1; } 61 | std::optional dangling{ return Func(); } // 编译通过,但是后续访问dangling是错误的。 62 | // 等价于:int* dangling = &(Func().value()); 63 | ``` 64 | 65 | 这是因为`std::optional`是临时的,自然指向它内部值的指针在其析构后也就悬垂了。 66 | 67 | 2. 略,比较简单。 68 | 69 | 3. 内部的`noexcept(a < b)`判断了`a < b`是否有noexcept specifier,如果是则得到true,否则为false,再配合外面的`noexcept`,于是表示当`a < b`为`noexcept`时,当前函数也为`noexcept`,否则反之。 70 | 71 | 4. 这个题比较麻烦,我们用基类的方式做一下: 72 | 73 | ```c++ 74 | #include 75 | #include 76 | 77 | template 78 | class ListBase 79 | { 80 | protected: 81 | ListNode sentinel_{ &sentinel_, &sentinel_ }; 82 | ~ListBase() 83 | { 84 | for (auto it = sentinel_.next; it != &sentinel_;) 85 | { 86 | it = it->next; 87 | delete it->prev; 88 | } 89 | 90 | // 错误写法:在it删除后继续使用。 91 | // for (auto it = sentinel_.next; it != &sentinel_; it = it->next) 92 | // delete it; 93 | } 94 | }; 95 | 96 | template 97 | class List : public ListBase 98 | { 99 | auto& GetSentinel_() { return this->sentinel_; } 100 | 101 | public: 102 | class ConstIterator 103 | { 104 | const ListNode *node_; 105 | 106 | public: 107 | ConstIterator(const ListNode *node) : node_{ node } {} 108 | ConstIterator operator++(int) noexcept 109 | { 110 | auto node0 = node_; 111 | node_ = node_->next; 112 | return ConstIterator{ node0 }; 113 | } 114 | 115 | ConstIterator &operator++() noexcept 116 | { 117 | node_ = node_->next; 118 | return *this; 119 | } 120 | const T &operator*() const noexcept { return node_->val; } 121 | const T *operator->() const noexcept { return &(node_->val); } 122 | bool operator==(ConstIterator another) const noexcept 123 | { 124 | return node_ == another.node_; 125 | } 126 | }; 127 | 128 | template 129 | List(It begin, It end) 130 | { 131 | auto pos = &this->sentinel_; 132 | while (begin != end) 133 | { 134 | std::unique_ptr> ptr{ new ListNode{ pos, pos->next, 135 | *begin } }; 136 | ++begin; 137 | 138 | pos->next->prev = ptr.get(); 139 | pos->next = ptr.release(); 140 | pos = pos->next; 141 | } 142 | return; 143 | } 144 | 145 | auto begin() const { return ConstIterator{ this->sentinel_.next }; } 146 | auto end() const { return ConstIterator{ &this->sentinel_ }; } 147 | 148 | List(const List& another) : List{ another.begin(), another.end() } {} 149 | void swap(List &another) noexcept 150 | { 151 | std::swap(GetSentinel_().prev, GetSentinel_().prev); 152 | std::swap(GetSentinel_().next, GetSentinel_().next); 153 | } 154 | 155 | List& operator=(const List &another) 156 | { 157 | // 尽管自赋值在Copy-and-swap中不用判断也能处理正确,但是提前离开减少拷贝也是有益的。 158 | if (this == &another) 159 | return *this; 160 | List list{ another }; 161 | swap(list); 162 | return *this; 163 | } 164 | }; 165 | ``` 166 | 167 | 5. 不能,因为如果`scores`插入时异常抛出,则`names`和`scores`的数量将会不一致。一种解决方法是把`name`和`score`合并为一个结构体,只保留一个`std::vector`;除此之外也可以进行`try-catch`,如果抛出了则看二者大小是否一致,不一致则pop出去一个,然后`throw;`重新抛出异常。我们在模板这一章会写一个通用的`PushbackGuard`来对任意数量的`std::vector`进行整体的数量保持。 168 | -------------------------------------------------------------------------------- /07-Error Handling/README.md: -------------------------------------------------------------------------------- 1 | # Assignment 2 | 3 | 1. 这道题讨论一下`optional`的性能问题,这是一个重要的问题。考虑下面的情况: 4 | 5 | ```c++ 6 | void Test(/* TYPE */ optVector) 7 | { 8 | if (optVector && !optVector->empty()) // short-circuit. 9 | std::println("{}", optVector[0]); 10 | return; 11 | } 12 | 13 | int main() 14 | { 15 | std::vector v{ 1,2,3 }; 16 | Test(v); 17 | return 0; 18 | } 19 | ``` 20 | 21 | + 当`TYPE`为`std::optional>`时,是否发生了拷贝? 22 | + 当`TYPE`为`const std::optional>&`时,是否发生了拷贝? 23 | + `TYPE`能否为`std::optional&>`? 24 | 25 | 可以看到,一旦涉及到`optional`,那么无论用户如何传递参数,都会导致效率问题。我们之前说过,引用不行,还有一种替代引用的类是? 26 | 27 | 没错,一种迂回的策略是使用`std::optional>>`。那么: 28 | 29 | + 当`TYPE`为`std::optional>>`时,`Test`的代码应该如何改动才能得到与之前相同的语义? 30 | + 除了上面的改动,你认为这种方式相比于`T*`有什么其他问题? 31 | 32 | 在[P2988](https://github.com/cplusplus/papers/issues/1661)引入了`std::optional`的特化,我个人认为这个提案的通过阻力应该比较小,主要的分歧在于`value_or`到底返回`T&`还是`T`,大概能顺利进入C++26,在作业答案中我们会给出其已经确定的使用方法。 33 | 34 | 2. 给第五节作业中`InplaceVector`增加异常处理相关的API。具体地: 35 | 36 | + 对于`PushBack`,如果大小大于buffer大小,则抛出`std::bad_alloc`。 37 | + 增加`.at()`,在`operator[]`的基础上进行边界检查,越界则抛出`std::out_of_range`。 38 | + 对显然的方法增加`noexcept`限定符。 39 | 40 | 3. 除了直接使用`noexcept`外,函数还允许`noexcept(bool)`来定义noexcept限定。即: 41 | 42 | ```c++ 43 | void Func() noexcept(true); // same as void Func() noexcept; 44 | void Func() noexcept(false); // same as void Func(); 45 | ``` 46 | 47 | 试解释以下声明方式: 48 | 49 | ```c++ 50 | template 51 | const T& min(const T& a, const T& b) noexcept(noexcept(a < b)) 52 | { 53 | return a < b ? a : b; 54 | } 55 | ``` 56 | 57 | 4. 练习下RAII和异常安全性,利用Copy-and-swap idiom进行强异常安全性保证。 58 | 59 | ```c++ 60 | template 61 | struct ListNode 62 | { 63 | ListNode* prev; 64 | ListNode* next; 65 | T val; // 正常来说应该是像InplaceVector一样搞个buffer 66 | // 这样就不用担心T不能默认构造的问题,我们简化处理 67 | }; 68 | 69 | template 70 | class List 71 | { 72 | // 可以修改到其他类里 73 | ListNode sentinel_{ &sentinel_, &sentinel_ }; 74 | 75 | // 如果你使用基类并把sentinel放到里面,this->是必需的。我们在模板会讲述原因。 76 | auto& GetSentinel_() { return this->sentinel_; } 77 | 78 | public: 79 | class ConstIterator 80 | { 81 | const ListNode *node_; 82 | 83 | public: 84 | ConstIterator(const ListNode *node) : node_{ node } {} 85 | ConstIterator operator++(int) noexcept 86 | { 87 | auto node0 = node_; 88 | node_ = node_->next; 89 | return ConstIterator{ node0 }; 90 | } 91 | 92 | ConstIterator &operator++() noexcept 93 | { 94 | node_ = node_->next; 95 | return *this; 96 | } 97 | const T &operator*() const noexcept { return node_->val; } 98 | const T *operator->() const noexcept { return &(node_->val); } 99 | bool operator==(const ConstIterator &another) const noexcept = default; 100 | }; 101 | 102 | template 103 | List(It begin, It end) 104 | { 105 | // TODO... 106 | // 如果构造失败,注意对分配的元素进行释放 107 | // 你可以选择使用标准库中VectorBase或VectorImpl一样的方法 108 | // 利用已经构造好的部分总会析构的特性 109 | // 也可以自己写一个Guard类承接sentinel并连接出链表,在退出前再Release给sentinel。 110 | } 111 | 112 | auto begin() const { return ConstIterator{ this->sentinel_.next }; } 113 | auto end() const { return ConstIterator{ &this->sentinel_ }; } 114 | 115 | // 委托构造函数(Delegating ctor),我记得好像是讲过了 116 | List(const List& another) : List{ another.begin(), another.end() } {} 117 | List& operator=(const List &another) 118 | { 119 | // TODO... 120 | } 121 | // 根据你的实现方式,确定是否需要手动写~List()。 122 | }; 123 | ``` 124 | 125 | 使用下面三段代码进行测试: 126 | 127 | ```c++ 128 | // 功能性测试,需要代码不崩溃正常退出。 129 | std::vector v{ 1,2,3 }; 130 | List l{ v.begin(), v.end() }; 131 | for (auto it = l.begin(); it != l.end(); it++) 132 | std::println("{}", *it); 133 | ``` 134 | 135 | ```c++ 136 | // 基本异常安全性测试,应该保证Constructed输出两次(拷贝导致的),Dtor输出六次(vector内三次+sentinel一次+异常安全性2次) 137 | class SomeClassMayThrow 138 | { 139 | int val_; 140 | public: 141 | SomeClassMayThrow(int val) : val_{val} {} 142 | SomeClassMayThrow(const SomeClassMayThrow &another) : val_{ another.val_ } 143 | { 144 | static int i = 0; 145 | if (i++ == 2) 146 | { 147 | throw std::runtime_error{ "Test" }; 148 | } 149 | std::println("Constructed."); 150 | } 151 | ~SomeClassMayThrow() { std::println("Dtor."); } 152 | auto GetVal() const noexcept { return val_; } 153 | }; 154 | 155 | std::vector a; 156 | a.reserve(3); 157 | a.emplace_back(1); 158 | a.emplace_back(2); 159 | a.emplace_back(3); 160 | try 161 | { 162 | List l{ a.begin(), a.end() }; 163 | } 164 | catch (const std::exception &ex) 165 | { 166 | std::println("{}", ex.what()); 167 | } 168 | ``` 169 | 170 | ```c++ 171 | // 强异常安全性测试,把上面"i++ == 2"改成"i++ == 5" 172 | std::vector a; 173 | a.reserve(3); 174 | a.emplace_back(1); 175 | a.emplace_back(2); 176 | a.emplace_back(3); 177 | List l{ a.begin(), a.end() }; 178 | List l2{ a.begin() + 1, a.end() }; 179 | try 180 | { 181 | l2 = l; 182 | } 183 | catch (const std::exception &ex) 184 | { 185 | std::println("{}", ex.what()); 186 | // 应当输出2, 3,即赋值失败对其无影响。 187 | for (auto it = l2.begin(); it != l2.end(); ++it) 188 | std::println("{}", it->GetVal()); 189 | } 190 | ``` 191 | 192 | 5. 假设一个类具有这样的不变量:它的学生名称数量和分数数量一致,即: 193 | 194 | ```c++ 195 | class A 196 | { 197 | public: 198 | void AddAttribute(); 199 | private: 200 | // Invariants: names.size() == scores.size(); 201 | std::vector names; 202 | std::vector scores; 203 | }; 204 | ``` 205 | 206 | 对下面的方法,`A`能够做到何种异常安全性的保证? 207 | 208 | ```c++ 209 | void A::AddAttribute(const std::string& name, int score) 210 | { 211 | names.push_back("Student: " + name); // strong exception guarantee. 212 | scores.push_back(score); // strong exception gurantee. 213 | return; 214 | } 215 | ``` 216 | 217 | 6. 使用Catch2将第4题的验证改写为单元测试,这可能需要把`SomeClassMayThrow`改为用成员控制何时抛出异常。你也可以自己添加更多的测试。 218 | 219 | -------------------------------------------------------------------------------- /07-Error Handling/code/copy-and-swap idiom.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | template 5 | class Vector 6 | { 7 | public: 8 | Vector(std::size_t num, const T& val){ 9 | std::unique_ptr arr{ new T[num] }; 10 | std::ranges::fill(arr.get(), arr.get() + num, val); 11 | first_ = arr.release(); 12 | last_ = end_ = first_ + num; 13 | } 14 | 15 | std::size_t size() const noexcept { return last_ - first_; } 16 | auto& operator[](std::size_t idx) noexcept { return first_[idx]; } 17 | const auto& operator[](std::size_t idx) const noexcept { return first_[idx]; } 18 | 19 | Vector(const Vector& another){ 20 | auto size = another.size(); 21 | std::unique_ptr arr{ new T[size] }; 22 | std::ranges::copy(another.first_, another.last_, arr.get()); 23 | first_ = arr.release(); 24 | last_ = end_ = first_ + size; 25 | } 26 | 27 | friend void swap(Vector& vec1, Vector& vec2) noexcept { 28 | std::ranges::swap(vec1.first_, vec2.first_); 29 | std::ranges::swap(vec1.last_, vec2.last); 30 | std::ranges::swap(vec1.end_, vec2.end_); 31 | } 32 | 33 | Vector& operator=(const Vector& another) { 34 | Vector vec{another}; 35 | swap(vec, *this); 36 | return *this; 37 | } 38 | 39 | private: 40 | T* first_, *last_, *end_; 41 | }; -------------------------------------------------------------------------------- /07-Error Handling/code/expected.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | std::expected parse_number(std::string_view &str) 8 | { 9 | double result; 10 | auto begin = str.data(); 11 | auto [end, ec] = std::from_chars(begin, begin + str.size(), result); 12 | 13 | if (ec != std::errc{}) 14 | return std::unexpected{ ec }; 15 | if (std::isinf(result)) // we regard inf as out of range too. 16 | return std::unexpected{ std::errc::result_out_of_range }; 17 | 18 | str.remove_prefix(end - begin); 19 | return result; 20 | } 21 | 22 | int main() 23 | { 24 | auto process = [](std::string_view str) { 25 | std::print("str: {:?}, ", str); 26 | parse_number(str) 27 | .transform([](double val) { 28 | std::println("value: {}", val); 29 | return val; 30 | }) 31 | .transform_error([](std::errc err) { 32 | if (err == std::errc::invalid_argument) 33 | std::println("error: invalid input"); 34 | else if (err == std::errc::result_out_of_range) 35 | std::println("error: overflow"); 36 | return err; 37 | }); 38 | }; 39 | 40 | for (auto src : { "42", "42abc", "meow", "inf" }) 41 | process(src); 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /07-Error Handling/code/optional.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | std::optional FindInStr(const std::string &str, auto pred) 7 | { 8 | // Of course, you can add std::reference_wrapper. 9 | auto pos = std::ranges::find_if(str, pred); 10 | return pos == str.end() ? std::nullopt : std::optional{ pos - str.begin() }; 11 | } 12 | 13 | int main() 14 | { 15 | std::string s{ "133" }; 16 | 17 | auto pos = FindInStr(s, [](char ch) { 18 | return ch >= '0' && ch <= '9' && (ch - '0') % 2 == 0; 19 | }); 20 | 21 | auto ch = pos.transform([&s](auto idx) { 22 | std::println("The first occurence: {}", s[idx]); 23 | return s[idx]; 24 | }) 25 | .or_else([]() { 26 | std::println("Not find any occurence"); 27 | return std::optional{ '?' }; 28 | }); 29 | 30 | std::println("Get char: {}", *ch); 31 | 32 | return 0; 33 | } -------------------------------------------------------------------------------- /07-Error Handling/code/xmake.lua: -------------------------------------------------------------------------------- 1 | set_languages("cxxlatest") 2 | add_rules("mode.debug", "mode.release") 3 | 4 | target("optional") 5 | add_files("optional.cpp") 6 | 7 | target("expected") 8 | add_files("expected.cpp") 9 | -------------------------------------------------------------------------------- /07-Error Handling/现代C++基础 - 错误处理.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/07-Error Handling/现代C++基础 - 错误处理.pdf -------------------------------------------------------------------------------- /08-String & Stream/code/Decompose.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // 大端法表示 6 | template 7 | void DecomposeToByte(const T& str) 8 | { 9 | for (int i = 0; i < str.size(); i++) 10 | { 11 | auto ch = static_cast(str[i]); 12 | unsigned int mask = (1 << CHAR_BIT) - 1; 13 | for (int j = 0; j < sizeof(str[0]); j++) 14 | { 15 | std::cout << std::format("{:02x} ", (mask & ch)); 16 | ch >>= 8; 17 | } 18 | } 19 | std::cout << "\n"; 20 | return; 21 | } 22 | 23 | int main() 24 | { 25 | std::wstring s1 = L"\U0000FFFF"; DecomposeToByte(s1); 26 | std::u8string s2 = u8"\U0001F449"; DecomposeToByte(s2); 27 | std::u16string s3 = u"\U0001F449"; DecomposeToByte(s3); 28 | std::u32string s4 = U"\U0001F449"; DecomposeToByte(s4); 29 | return 0; 30 | } -------------------------------------------------------------------------------- /08-String & Stream/code/Profile.cpp: -------------------------------------------------------------------------------- 1 | //******************************************************** 2 | // The following code example is taken from the book 3 | // C++20 - The Complete Guide 4 | // by Nicolai M. Josuttis (www.josuttis.com) 5 | // http://www.cppstd20.com 6 | // 7 | // The code is licensed under a 8 | // Creative Commons Attribution 4.0 International License 9 | // http://creativecommons.org/licenses/by/4.0/ 10 | //******************************************************** 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | static void checkSPrintf(int num) 21 | { 22 | for (int idx = 0; idx < num; ++idx) 23 | { 24 | int i = 42; 25 | double d = 7.7; 26 | char buf[100] = { '?', '?', '?', '?', '?', '?', '?', 27 | '?', '?', '?', '?', '?', '?' }; 28 | sprintf(buf, "%d %f", i, d); 29 | if (num == 1) 30 | { 31 | std::cout << buf << '\n'; 32 | return; 33 | } 34 | } 35 | } 36 | 37 | static void checkOStringStream(int num) 38 | { 39 | for (int idx = 0; idx < num; ++idx) 40 | { 41 | int i = 42; 42 | double d = 7.7; 43 | std::ostringstream os; 44 | os << i << ' ' << d; 45 | if (num == 1) 46 | { 47 | std::cout << os.str() << '\n'; 48 | return; 49 | } 50 | } 51 | } 52 | 53 | static void checkToString(int num) 54 | { 55 | for (int idx = 0; idx < num; ++idx) 56 | { 57 | int i = 42; 58 | double d = 7.7; 59 | std::string s = std::to_string(i) + ' ' + std::to_string(d); 60 | if (num == 1) 61 | { 62 | std::cout << s << '\n'; 63 | return; 64 | } 65 | } 66 | } 67 | 68 | static void checkToChars(int num) 69 | { 70 | for (int idx = 0; idx < num; ++idx) 71 | { 72 | int i = 42; 73 | double d = 7.7; 74 | char buf[100] = { '?', '?', '?', '?', '?', '?', '?', 75 | '?', '?', '?', '?', '?', '?' }; 76 | std::to_chars_result res = std::to_chars(buf, buf + 9, i); 77 | *res.ptr = ' '; 78 | res = std::to_chars(res.ptr + 1, res.ptr + 10, d); 79 | *res.ptr = '\0'; 80 | if (num == 1) 81 | { 82 | std::cout << buf << '\n'; 83 | return; 84 | } 85 | } 86 | } 87 | 88 | static void checkFormat(int num) 89 | { 90 | for (int idx = 0; idx < num; ++idx) 91 | { 92 | int i = 42; 93 | double d = 7.7; 94 | auto s = std::format("{} {}", i, d); 95 | if (num == 1) 96 | { 97 | std::cout << s << '\n'; 98 | return; 99 | } 100 | } 101 | } 102 | 103 | static void checkFormatTo(int num) 104 | { 105 | for (int idx = 0; idx < num; ++idx) 106 | { 107 | int i = 42; 108 | double d = 7.7; 109 | char buf[100] = { '?', '?', '?', '?', '?', '?', '?', 110 | '?', '?', '?', '?', '?', '?' }; 111 | auto ret = std::format_to_n(buf, 99, "{} {}", i, d); 112 | *(ret.out) = '\0'; 113 | if (num == 1) 114 | { 115 | std::cout << buf << '\n'; 116 | return; 117 | } 118 | } 119 | } 120 | 121 | template 122 | void measure(std::string s, T func) 123 | { 124 | auto t0 = std::chrono::steady_clock::now(); 125 | func(1000); // measure 126 | auto t1 = std::chrono::steady_clock::now(); 127 | std::chrono::duration diff{ t1 - t0 }; 128 | std::cout << s << ": " << diff.count() << "ms\n "; 129 | func(1); // show output 130 | } 131 | 132 | int main() 133 | { 134 | measure("sprintf() ", checkSPrintf); 135 | measure("ostringstream", checkOStringStream); 136 | measure("to_string() ", checkToString); 137 | measure("to_chars() ", checkToChars); 138 | measure("format() ", checkFormat); 139 | measure("format_to() ", checkFormatTo); 140 | return 0; 141 | } 142 | -------------------------------------------------------------------------------- /08-String & Stream/code/RegexTest.cpp: -------------------------------------------------------------------------------- 1 | #include "ctre.hpp" 2 | #include "re2/re2.h" 3 | #include 4 | 5 | // 注意RE2有一段额外的括号,因为不会默认进行全局的捕获。 6 | static constexpr re2::StringPiece re2Regex{ R"(((\d+)\.(\d+)\.(\d+)\.(\d+)))" }; 7 | static constexpr CTRE_REGEX_INPUT_TYPE ctreRegex{ 8 | R"((\d+)\.(\d+)\.(\d+)\.(\d+))" 9 | }; 10 | static const std::string singleStr = "127.0.0.1"; 11 | static const std::string multiStr = "Test: 127.0.0.1; Test2: 127.0.0.2aaa"; 12 | 13 | void MatchTest() 14 | { 15 | re2::StringPiece whole1; 16 | int a1, b1, c1, d1; 17 | if (RE2::FullMatch(singleStr, re2Regex, &whole1, &a1, &b1, &c1, &d1)) 18 | { 19 | std::println("re2 Pass: {} {} {} {} {}", whole1, a1, b1, c1, d1); 20 | } 21 | else 22 | { 23 | std::println("Fail"); 24 | } 25 | 26 | // Notice: structured binding in if clause is not regulated in the standard 27 | // So the second whole2 is necessary. MSVC rejects it if it's not provided. 28 | if (auto [whole2, a2, b2, c2, d2] = ctre::match(singleStr); 29 | whole2) 30 | { 31 | std::println("ctre Pass: {} {} {} {} {}", whole2.to_view(), 32 | a2.to_number(), b2.to_number(), c2.to_number(), 33 | d2.to_number()); 34 | } 35 | else 36 | { 37 | std::println("Fail"); 38 | } 39 | } 40 | 41 | void SearchTest() 42 | { 43 | re2::StringPiece whole; 44 | if (RE2::PartialMatch(multiStr, re2Regex, &whole)) 45 | { 46 | std::println("re2 Pass: {}", whole); 47 | } 48 | else 49 | { 50 | std::println("Fail"); 51 | } 52 | 53 | if (auto m = ctre::search(singleStr)) 54 | { 55 | std::println("ctre Pass: {}", m.to_view()); 56 | } 57 | else 58 | { 59 | std::println("Fail"); 60 | } 61 | } 62 | 63 | void SearchAllTest() 64 | { 65 | re2::StringPiece whole, multiStrView{ multiStr }; 66 | while (RE2::FindAndConsume(&multiStrView, re2Regex, &whole)) 67 | { 68 | std::println("re2 Pass: {}", whole); 69 | } 70 | 71 | for (auto m : ctre::search_all(multiStr)) 72 | { 73 | std::println("ctre Pass: {}", m.to_view()); 74 | } 75 | } 76 | 77 | // re2 only 78 | void ReplaceTest() 79 | { 80 | std::string newString{ multiStr }; 81 | // 用第二个捕获(127)进行替换 82 | if (RE2::Replace(&newString, re2Regex, "\\2")) 83 | std::println("re2 Replace test: {}", newString); 84 | newString = multiStr; 85 | if (RE2::GlobalReplace(&newString, re2Regex, "SOME_IP\\2")) 86 | std::println("re2 Global replace test: {}", newString); 87 | } 88 | 89 | // ctre only 90 | void TokenizeTest() 91 | { 92 | for (auto token : ctre::tokenize("127.0.0.1")) 93 | std::print("ctre tokenize: {} ", token.to_view()); 94 | std::print("\n"); 95 | for (auto m : ctre::split(multiStr)) 96 | std::println("ctre split: {} {}", m.to_view(), m.get<1>().to_view()); 97 | } 98 | 99 | int main() 100 | { 101 | MatchTest(); 102 | SearchTest(); 103 | SearchAllTest(); 104 | ReplaceTest(); 105 | TokenizeTest(); 106 | return 0; 107 | } -------------------------------------------------------------------------------- /08-String & Stream/code/xmake.lua: -------------------------------------------------------------------------------- 1 | set_languages("cxxlatest") 2 | set_encodings("utf-8") 3 | set_warnings("all") 4 | set_policy("build.warning", true) 5 | add_rules("mode.debug", "mode.release") 6 | 7 | add_requires("re2", "ctre") 8 | 9 | target("Decompose") 10 | add_files("Decompose.cpp") 11 | 12 | target("Profile") 13 | add_files("Profile.cpp") 14 | 15 | target("RegexTest") 16 | add_packages("re2", "ctre") 17 | add_files("RegexTest.cpp") -------------------------------------------------------------------------------- /08-String & Stream/example-answer/src/Socket.cpp: -------------------------------------------------------------------------------- 1 | #include "Socket.h" 2 | #include 3 | 4 | #ifdef _WIN32 5 | 6 | #include 7 | #pragma comment(lib, "Ws2_32.lib") 8 | 9 | #else 10 | 11 | #include 12 | #include 13 | 14 | #endif 15 | 16 | namespace Network 17 | { 18 | 19 | Socket::Socket(const char *ip, std::uint16_t port, Tag tag) 20 | { 21 | if (tag == Tag::Listen) 22 | { 23 | CreateListenSocket_(ip, port); 24 | } 25 | else if (tag == Tag::Connect) 26 | { 27 | CreateConnectSocket_(ip, port); 28 | } 29 | else [[unlikely]] 30 | { 31 | throw std::runtime_error{ "Unknown tag.\n" }; 32 | } 33 | } 34 | 35 | Socket::Socket(const Socket &listenSock, Tag tag) 36 | { 37 | if (tag != Tag::Accept) [[unlikely]] 38 | { 39 | throw std::runtime_error{ "Unknown tag.\n" }; 40 | } 41 | 42 | sockaddr_in addr; 43 | socklen_t addrLen = sizeof(addr); 44 | socket_ = ::accept(listenSock.socket_, reinterpret_cast(&addr), 45 | &addrLen); 46 | } 47 | 48 | bool Socket::CreateSocketCommon_(const char *ip, std::uint16_t port, 49 | sockaddr_in &addr) 50 | { 51 | addr = {}; 52 | addr.sin_family = AF_INET; 53 | if (::inet_pton(AF_INET, ip, &addr.sin_addr) == 0) // Wrong IP format. 54 | { 55 | return false; 56 | } 57 | addr.sin_port = ::htons(port); 58 | socket_ = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 59 | return socket_ != s_invalidSocket_; 60 | } 61 | 62 | void Socket::CreateListenSocket_(const char *ip, std::uint16_t port) 63 | { 64 | sockaddr_in addr; 65 | if (!CreateSocketCommon_(ip, port, addr)) 66 | { 67 | return; 68 | } 69 | 70 | if (::bind(socket_, reinterpret_cast(&addr), sizeof(addr)) == 71 | -1) 72 | { 73 | Close(); 74 | } 75 | 76 | if (::listen(socket_, -1) == -1) 77 | { 78 | Close(); 79 | } 80 | } 81 | 82 | void Socket::CreateConnectSocket_(const char *ip, std::uint16_t port) 83 | { 84 | sockaddr_in addr; 85 | if (!CreateSocketCommon_(ip, port, addr)) 86 | { 87 | return; 88 | } 89 | 90 | if (::connect(socket_, reinterpret_cast(&addr), sizeof(addr)) == 91 | -1) 92 | { 93 | Close(); 94 | } 95 | } 96 | 97 | void Socket::Clean_() 98 | { 99 | #ifdef _WIN32 100 | ::closesocket(socket_); 101 | #else 102 | ::close(socket_); 103 | #endif 104 | } 105 | 106 | } // namespace Network -------------------------------------------------------------------------------- /08-String & Stream/example-answer/src/Socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | 5 | #define NOMINMAX 6 | #include 7 | 8 | #pragma comment(lib, "Ws2_32.lib") 9 | 10 | #else 11 | 12 | #include 13 | #include 14 | 15 | #endif 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace Network 23 | { 24 | 25 | class Socket 26 | { 27 | #ifdef _WIN32 28 | inline static constexpr SOCKET s_invalidSocket_ = INVALID_SOCKET; 29 | SOCKET socket_{ s_invalidSocket_ }; 30 | #else 31 | inline static constexpr int s_invalidSocket_ = -1; 32 | int socket_{ s_invalidSocket_ }; 33 | #endif 34 | 35 | public: 36 | enum class Tag 37 | { 38 | Listen, 39 | Accept, 40 | Connect 41 | }; 42 | 43 | Socket() = default; 44 | 45 | // Listen socket or connect socket. 46 | Socket(const char *ip, std::uint16_t port, Tag tag); 47 | 48 | // Accept socket; To distinguish it from copy ctor (in fact socket doesn't 49 | // have it), add a tag param. 50 | Socket(const Socket &listenSock, Tag tag); 51 | 52 | Socket(Socket &&another) noexcept 53 | : socket_{ std::exchange(another.socket_, s_invalidSocket_) } 54 | { 55 | } 56 | Socket &operator=(Socket &&another) noexcept 57 | { 58 | if (this == &another) 59 | { 60 | return *this; 61 | } 62 | if (*this) 63 | Clean_(); 64 | socket_ = std::exchange(another.socket_, s_invalidSocket_); 65 | return *this; 66 | } 67 | 68 | ~Socket() 69 | { 70 | if (*this) 71 | Clean_(); 72 | } 73 | 74 | // Clean and reset. 75 | void Close() 76 | { 77 | if (*this) 78 | { 79 | Clean_(); 80 | socket_ = s_invalidSocket_; 81 | } 82 | } 83 | explicit operator bool() const noexcept 84 | { 85 | return socket_ != s_invalidSocket_; 86 | } 87 | auto GetHandle() const noexcept { return socket_; } 88 | 89 | private: 90 | bool CreateSocketCommon_(const char *ip, std::uint16_t port, 91 | sockaddr_in &addr); 92 | void CreateListenSocket_(const char *ip, std::uint16_t port); 93 | void CreateConnectSocket_(const char *ip, std::uint16_t port); 94 | void Clean_(); 95 | }; 96 | 97 | inline auto GetErrorCode() 98 | { 99 | #ifdef _WIN32 100 | return WSAGetLastError(); 101 | #else 102 | return errno; 103 | #endif 104 | } 105 | 106 | inline bool Startup() 107 | { 108 | #ifdef _WIN32 109 | WSADATA wsaData; 110 | if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) 111 | { 112 | return false; 113 | } 114 | #endif 115 | return true; 116 | } 117 | 118 | } // namespace Network 119 | -------------------------------------------------------------------------------- /08-String & Stream/example-answer/src/TCPStream.h: -------------------------------------------------------------------------------- 1 | #include "TCPBuf.h" 2 | #include 3 | #include 4 | 5 | namespace Network 6 | { 7 | class OTCPStream : public std::basic_ostream 8 | { 9 | using Base = std::basic_ostream; 10 | 11 | public: 12 | OTCPStream() : Base{ &buf_ } {} 13 | 14 | void open(Socket &&socket, std::streamsize outSize) 15 | { 16 | buf_.open(std::move(socket), std::ios::out, 0, outSize); 17 | } 18 | 19 | void close() { buf_.close(); } 20 | 21 | bool is_open() const noexcept { return buf_.is_open(); } 22 | 23 | const TCPBuf *rdbuf() const noexcept { return &buf_; } 24 | TCPBuf *rdbuf() noexcept { return &buf_; } 25 | 26 | private: 27 | TCPBuf buf_; 28 | }; 29 | 30 | class ITCPStream : public std::basic_istream 31 | { 32 | using Base = std::basic_istream; 33 | 34 | public: 35 | ITCPStream() : Base{ &buf_ } {} 36 | 37 | void open(Socket &&socket, std::streamsize inSize) 38 | { 39 | buf_.open(std::move(socket), std::ios::in, inSize, 0); 40 | } 41 | 42 | void close() { buf_.close(); } 43 | 44 | bool is_open() const noexcept { return buf_.is_open(); } 45 | 46 | const TCPBuf *rdbuf() const noexcept { return &buf_; } 47 | TCPBuf *rdbuf() noexcept { return &buf_; } 48 | 49 | private: 50 | TCPBuf buf_; 51 | }; 52 | 53 | } // namespace Network -------------------------------------------------------------------------------- /08-String & Stream/example-answer/src/client.cpp: -------------------------------------------------------------------------------- 1 | #include "TCPStream.h" 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | Network::Startup(); 9 | Network::Socket connectSocket{ "127.0.0.1", 34567, 10 | Network::Socket::Tag::Connect }; 11 | if (!connectSocket) 12 | { 13 | std::println("Connect socket error: {}", Network::GetErrorCode()); 14 | return 1; 15 | } 16 | 17 | Network::OTCPStream stream; 18 | stream.open(std::move(connectSocket), 4); 19 | std::string str; 20 | for (int i = 0; i < 10; i++) 21 | { 22 | std::cin >> str; 23 | // 这里改成endl是为了让接收方输入字串在空白处停止,否则会一直尝试读入。 24 | stream << str << std::endl; 25 | } 26 | 27 | return 0; 28 | } -------------------------------------------------------------------------------- /08-String & Stream/example-answer/src/server.cpp: -------------------------------------------------------------------------------- 1 | #include "TCPStream.h" 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | Network::Startup(); 9 | Network::Socket socket{ "127.0.0.1", 34567, Network::Socket::Tag::Listen }; 10 | if (!socket) 11 | { 12 | std::println("Listen socket error: {}", Network::GetErrorCode()); 13 | return 1; 14 | } 15 | Network::Socket acceptSock{ socket, Network::Socket::Tag::Accept }; 16 | if (!acceptSock) 17 | { 18 | std::println("Accept socket error: {}", Network::GetErrorCode()); 19 | return 1; 20 | } 21 | 22 | Network::ITCPStream stream; 23 | stream.open(std::move(acceptSock), 4); 24 | std::string str; 25 | while (stream >> str) 26 | { 27 | std::cout << str << std::endl; 28 | } 29 | return 0; 30 | } -------------------------------------------------------------------------------- /08-String & Stream/example-answer/xmake.lua: -------------------------------------------------------------------------------- 1 | set_languages("cxxlatest") 2 | set_policy("build.warning", true) 3 | set_warnings("all") 4 | 5 | set_encodings("utf-8") 6 | 7 | add_rules("mode.debug", "mode.release") 8 | 9 | target("TCPStream") 10 | set_kind("static") 11 | add_files("src/Socket.cpp") 12 | 13 | target("server") 14 | add_deps("TCPStream") 15 | add_files("src/server.cpp") 16 | 17 | target("client") 18 | add_deps("TCPStream") 19 | add_files("src/client.cpp") -------------------------------------------------------------------------------- /08-String & Stream/example/src/Socket.cpp: -------------------------------------------------------------------------------- 1 | #include "Socket.h" 2 | #include 3 | 4 | #ifdef _WIN32 5 | 6 | #include 7 | #pragma comment(lib, "Ws2_32.lib") 8 | 9 | #else 10 | 11 | #include 12 | #include 13 | 14 | #endif 15 | 16 | namespace Network 17 | { 18 | 19 | Socket::Socket(const char *ip, std::uint16_t port, Tag tag) 20 | { 21 | if (tag == Tag::Listen) 22 | { 23 | CreateListenSocket_(ip, port); 24 | } 25 | else if (tag == Tag::Connect) 26 | { 27 | CreateConnectSocket_(ip, port); 28 | } 29 | else [[unlikely]] 30 | { 31 | throw std::runtime_error{ "Unknown tag.\n" }; 32 | } 33 | } 34 | 35 | Socket::Socket(const Socket &listenSock, Tag tag) 36 | { 37 | if (tag != Tag::Accept) [[unlikely]] 38 | { 39 | throw std::runtime_error{ "Unknown tag.\n" }; 40 | } 41 | 42 | sockaddr_in addr; 43 | socklen_t addrLen = sizeof(addr); 44 | socket_ = ::accept(listenSock.socket_, reinterpret_cast(&addr), 45 | &addrLen); 46 | } 47 | 48 | bool Socket::CreateSocketCommon_(const char *ip, std::uint16_t port, 49 | sockaddr_in &addr) 50 | { 51 | addr = {}; 52 | addr.sin_family = AF_INET; 53 | if (::inet_pton(AF_INET, ip, &addr.sin_addr) == 0) // Wrong IP format. 54 | { 55 | return false; 56 | } 57 | addr.sin_port = ::htons(port); 58 | socket_ = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 59 | return socket_ != s_invalidSocket_; 60 | } 61 | 62 | void Socket::CreateListenSocket_(const char *ip, std::uint16_t port) 63 | { 64 | sockaddr_in addr; 65 | if (!CreateSocketCommon_(ip, port, addr)) 66 | { 67 | return; 68 | } 69 | 70 | if (::bind(socket_, reinterpret_cast(&addr), sizeof(addr)) == 71 | -1) 72 | { 73 | Close(); 74 | } 75 | 76 | if (::listen(socket_, -1) == -1) 77 | { 78 | Close(); 79 | } 80 | } 81 | 82 | void Socket::CreateConnectSocket_(const char *ip, std::uint16_t port) 83 | { 84 | sockaddr_in addr; 85 | if (!CreateSocketCommon_(ip, port, addr)) 86 | { 87 | return; 88 | } 89 | 90 | if (::connect(socket_, reinterpret_cast(&addr), sizeof(addr)) == 91 | -1) 92 | { 93 | Close(); 94 | } 95 | } 96 | 97 | void Socket::Clean_() 98 | { 99 | #ifdef _WIN32 100 | ::closesocket(socket_); 101 | #else 102 | ::close(socket_); 103 | #endif 104 | } 105 | 106 | } // namespace Network -------------------------------------------------------------------------------- /08-String & Stream/example/src/Socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | 5 | #define NOMINMAX 6 | #include 7 | 8 | #pragma comment(lib, "Ws2_32.lib") 9 | 10 | #else 11 | 12 | #include 13 | #include 14 | 15 | #endif 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace Network 23 | { 24 | 25 | class Socket 26 | { 27 | #ifdef _WIN32 28 | inline static constexpr SOCKET s_invalidSocket_ = INVALID_SOCKET; 29 | SOCKET socket_{ s_invalidSocket_ }; 30 | #else 31 | inline static constexpr int s_invalidSocket_ = -1; 32 | int socket_{ s_invalidSocket_ }; 33 | #endif 34 | 35 | public: 36 | enum class Tag 37 | { 38 | Listen, 39 | Accept, 40 | Connect 41 | }; 42 | 43 | Socket() = default; 44 | 45 | // Listen socket or connect socket. 46 | Socket(const char *ip, std::uint16_t port, Tag tag); 47 | 48 | // Accept socket; To distinguish it from copy ctor (in fact socket doesn't 49 | // have it), add a tag param. 50 | Socket(const Socket &listenSock, Tag tag); 51 | 52 | Socket(Socket &&another) noexcept 53 | : socket_{ std::exchange(another.socket_, s_invalidSocket_) } 54 | { 55 | } 56 | Socket &operator=(Socket &&another) noexcept 57 | { 58 | if (*this) 59 | Clean_(); 60 | socket_ = std::exchange(another.socket_, s_invalidSocket_); 61 | return *this; 62 | } 63 | 64 | ~Socket() 65 | { 66 | if (*this) 67 | Clean_(); 68 | } 69 | 70 | // Clean and reset. 71 | void Close() 72 | { 73 | if (*this) 74 | { 75 | Clean_(); 76 | socket_ = s_invalidSocket_; 77 | } 78 | } 79 | explicit operator bool() const noexcept 80 | { 81 | return socket_ != s_invalidSocket_; 82 | } 83 | auto GetHandle() const noexcept { return socket_; } 84 | 85 | private: 86 | bool CreateSocketCommon_(const char *ip, std::uint16_t port, 87 | sockaddr_in &addr); 88 | void CreateListenSocket_(const char *ip, std::uint16_t port); 89 | void CreateConnectSocket_(const char *ip, std::uint16_t port); 90 | void Clean_(); 91 | }; 92 | 93 | inline auto GetErrorCode() 94 | { 95 | #ifdef _WIN32 96 | return WSAGetLastError(); 97 | #else 98 | return errno; 99 | #endif 100 | } 101 | 102 | inline bool Startup() 103 | { 104 | #ifdef _WIN32 105 | WSADATA wsaData; 106 | if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) 107 | { 108 | return false; 109 | } 110 | #endif 111 | return true; 112 | } 113 | 114 | } // namespace Network 115 | -------------------------------------------------------------------------------- /08-String & Stream/example/src/TCPBuf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Socket.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifndef _WIN32 13 | #include 14 | #endif 15 | 16 | namespace Network 17 | { 18 | 19 | template 20 | class UserManagableBuffer 21 | { 22 | bool userManaged_ = false; 23 | T *buffer_ = nullptr; 24 | std::streamsize bufferSize_ = 0; 25 | 26 | public: 27 | UserManagableBuffer() = default; 28 | UserManagableBuffer(std::streamsize size) 29 | : buffer_{ new T[size] }, bufferSize_{ size } 30 | { 31 | } 32 | UserManagableBuffer(const UserManagableBuffer &) = delete; 33 | UserManagableBuffer &operator=(const UserManagableBuffer &) = delete; 34 | UserManagableBuffer(UserManagableBuffer &&another) noexcept 35 | : userManaged_{ std::exchange(another.userManaged_, false) }, 36 | buffer_{ std::exchange(another.buffer_, nullptr) }, 37 | bufferSize_{ std::exchange(another.bufferSize_, 0) } 38 | { 39 | } 40 | UserManagableBuffer &operator=(UserManagableBuffer &&another) noexcept 41 | { 42 | if (!userManaged_) 43 | { 44 | delete[] buffer_; 45 | } 46 | 47 | userManaged_ = std::exchange(another.userManaged_, false); 48 | buffer_ = std::exchange(another.buffer_, nullptr); 49 | bufferSize_ = std::exchange(another.bufferSize_, 0); 50 | return *this; 51 | } 52 | ~UserManagableBuffer() 53 | { 54 | if (!userManaged_) 55 | { 56 | delete[] buffer_; 57 | } 58 | } 59 | 60 | auto begin() const noexcept { return buffer_; } 61 | auto end() const noexcept { return buffer_ + bufferSize_; } 62 | 63 | auto GetRawBuffer() const noexcept { return buffer_; } 64 | auto GetSize() const noexcept { return bufferSize_; } 65 | 66 | void SetBuffer(T *s, std::streamsize n) 67 | { 68 | auto oldBuffer = buffer_, needDelete = !userManaged_; 69 | if (s == nullptr) 70 | { 71 | // 如果buffer size不变,且之前也是自动管理的,就不要再重新分配了 72 | if (n == bufferSize_ && !userManaged_) 73 | return; 74 | buffer_ = n == 0 ? nullptr : new T[n]; 75 | bufferSize_ = n; 76 | userManaged_ = false; 77 | } 78 | else 79 | { 80 | buffer_ = s; 81 | bufferSize_ = n; 82 | userManaged_ = true; 83 | } 84 | if (needDelete) 85 | delete[] oldBuffer; 86 | } 87 | }; 88 | 89 | class TCPBuf : public std::basic_streambuf 90 | { 91 | static_assert(sizeof(char_type) == 1); 92 | using Base = std::basic_streambuf; 93 | static inline constexpr int_type s_EOF_ = traits_type::eof(); 94 | static inline constexpr int_type s_NotEOF_ = traits_type::not_eof(0); 95 | 96 | public: 97 | TCPBuf() = default; 98 | TCPBuf(const TCPBuf &) = delete; 99 | TCPBuf(TCPBuf &&another) = default; 100 | ~TCPBuf() override { FlushBuffer_(); } 101 | 102 | TCPBuf *open(Socket &&socket, std::ios::openmode mode, 103 | std::streamsize inSize, std::streamsize outSize) 104 | { 105 | UserManagableBuffer inBuffer, outBuffer; 106 | if (mode & std::ios::in) 107 | { 108 | inBuffer.SetBuffer(nullptr, inSize); 109 | } 110 | 111 | if (mode & std::ios::out) 112 | { 113 | outBuffer.SetBuffer(nullptr, outSize); 114 | } 115 | 116 | // 分配了buffer之后,还要设置指针 117 | setg(inBuffer.begin(), inBuffer.end(), inBuffer.end()); 118 | setp(outBuffer.begin(), outBuffer.end()); 119 | 120 | socket_ = std::move(socket); 121 | inBuffer = std::move(inBuffer), outBuffer_ = std::move(outBuffer); 122 | return this; 123 | } 124 | 125 | bool is_open() const noexcept { return static_cast(socket_); } 126 | 127 | TCPBuf *close() noexcept 128 | { 129 | FlushBuffer_(); 130 | socket_.Close(); 131 | inBuffer_.SetBuffer(nullptr, 0); 132 | outBuffer_.SetBuffer(nullptr, 0); 133 | setg(nullptr, nullptr, nullptr); 134 | setp(nullptr, nullptr); 135 | return this; 136 | } 137 | 138 | // overflow -> 溢出的时候调用 139 | // sync -> flush调用 140 | // xsputn -> bulk write 141 | private: 142 | // 返回未写入的大小 143 | std::streamsize SendAsMuchAsPossible_(const char *ptr, std::streamsize size) 144 | { 145 | assert(size <= std::numeric_limits::max()); 146 | while (size != 0) 147 | { 148 | int resultSize = 149 | ::send(socket_.GetHandle(), ptr, static_cast(size), 0); 150 | if (resultSize <= 0) 151 | { 152 | break; 153 | } 154 | size -= resultSize, ptr += resultSize; 155 | } 156 | return size; 157 | } 158 | 159 | auto GetOutputRemainSize_() const noexcept { return epptr() - pptr(); } 160 | 161 | void MemcpyToOutputBuffer_(const char_type *ptr, std::streamsize size) 162 | { 163 | // 拷贝到pptr里,然后前进pptr 164 | std::memcpy(pptr(), ptr, size); 165 | pbump(size); 166 | return; 167 | } 168 | 169 | protected: 170 | int_type overflow(int_type ch = s_EOF_) override 171 | { 172 | if (!socket_) 173 | return s_EOF_; 174 | 175 | if (traits_type::eq_int_type(ch, s_EOF_)) 176 | return s_NotEOF_; 177 | 178 | if (outBuffer_.GetRawBuffer() == nullptr) 179 | { 180 | char_type realCh = ch; 181 | 182 | return ::send(socket_.GetHandle(), &realCh, sizeof(realCh), 0) == 183 | sizeof(realCh) 184 | ? s_NotEOF_ 185 | : s_EOF_; 186 | } 187 | 188 | // 腾出空间,写字节 189 | FlushBuffer_(); 190 | if (GetOutputRemainSize_() == 0) 191 | { 192 | return s_EOF_; 193 | } 194 | // 把字节写进去 195 | *this->pptr() = ch; 196 | this->pbump(1); 197 | return s_NotEOF_; 198 | } 199 | 200 | std::streamsize xsputn(const char_type *s, std::streamsize count) override 201 | { 202 | if (!socket_) 203 | return 0; 204 | 205 | if (GetOutputRemainSize_() >= count) 206 | { 207 | MemcpyToOutputBuffer_(s, count); 208 | return count; 209 | } 210 | 211 | std::streamsize successSize = 0; 212 | if (FlushBuffer_()) 213 | { 214 | auto failSize = SendAsMuchAsPossible_(s, count); 215 | if (failSize == 0) 216 | { 217 | return count; 218 | } 219 | successSize = count - failSize; 220 | s += successSize, count = failSize; 221 | } 222 | // 若flushBuffer没有完全写入,则尽可能拷贝 223 | // 若未完全发送用户buffer,则把剩余部分拷贝过来 224 | auto largestSize = std::min(GetOutputRemainSize_(), count); 225 | MemcpyToOutputBuffer_(s, largestSize); 226 | return successSize + largestSize; 227 | } 228 | 229 | int sync() override { return FlushBuffer_() ? 0 : -1; } 230 | 231 | bool FlushBuffer_() 232 | { 233 | // [pbase, pptr) 已经有内容,进行刷新(全部写出) 234 | auto begPtr = this->pbase(); 235 | auto msgSize = this->pptr() - begPtr; 236 | if (msgSize == 0) 237 | { 238 | return true; 239 | } 240 | 241 | // 不是空就要刷新 242 | auto failSize = SendAsMuchAsPossible_(begPtr, msgSize); 243 | if (failSize == 0) 244 | { 245 | this->setp(outBuffer_.begin(), outBuffer_.end()); 246 | return true; 247 | } 248 | this->setp(this->pptr() - failSize, this->epptr()); 249 | this->pbump(static_cast(failSize)); 250 | return false; 251 | } 252 | 253 | // --------------- Input related ----------------- 254 | std::streamsize showmanyc() override 255 | { 256 | unsigned long size = 0; 257 | 258 | // Unknown length 259 | #ifdef _WIN32 260 | if (::ioctlsocket(socket_.GetHandle(), FIONREAD, &size) == SOCKET_ERROR) 261 | return 0; 262 | #else 263 | if (::ioctl(socket_.GetHandle(), FIONREAD, &size) < 0) 264 | return 0; 265 | #endif 266 | static constexpr auto limit = 267 | std::numeric_limits::max(); 268 | if constexpr (std::numeric_limits::max() <= limit) 269 | { 270 | return size == 0 ? -1 : static_cast(size); 271 | } 272 | else 273 | { 274 | // This is safe though conversion may happen, since limit >= 0 and 275 | // size >= 0. 276 | auto ret = std::min(static_cast(size), limit); 277 | return ret == 0 ? -1 : ret; 278 | } 279 | } 280 | 281 | private: 282 | Socket socket_; 283 | UserManagableBuffer inBuffer_; 284 | UserManagableBuffer outBuffer_; 285 | }; 286 | 287 | } // namespace Network -------------------------------------------------------------------------------- /08-String & Stream/example/src/TCPStream.h: -------------------------------------------------------------------------------- 1 | #include "TCPBuf.h" 2 | #include 3 | 4 | namespace Network 5 | { 6 | class OTCPStream : public std::basic_ostream 7 | { 8 | using Base = std::basic_ostream; 9 | 10 | public: 11 | OTCPStream() : Base{ &buf_ } {} 12 | 13 | void open(Socket &&socket, std::streamsize outSize) 14 | { 15 | buf_.open(std::move(socket), std::ios::out, 0, outSize); 16 | } 17 | 18 | void close() { buf_.close(); } 19 | 20 | bool is_open() const noexcept { return buf_.is_open(); } 21 | 22 | const TCPBuf *rdbuf() const noexcept { return &buf_; } 23 | TCPBuf *rdbuf() noexcept { return &buf_; } 24 | 25 | private: 26 | TCPBuf buf_; 27 | }; 28 | } // namespace Network -------------------------------------------------------------------------------- /08-String & Stream/example/src/client.cpp: -------------------------------------------------------------------------------- 1 | #include "TCPStream.h" 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | Network::Startup(); 9 | Network::Socket connectSocket{ "127.0.0.1", 34567, 10 | Network::Socket::Tag::Connect }; 11 | if (!connectSocket) 12 | { 13 | std::println("Connect socket error: {}", Network::GetErrorCode()); 14 | return 1; 15 | } 16 | 17 | Network::OTCPStream stream; 18 | stream.open(std::move(connectSocket), 4); 19 | std::string str; 20 | for (int i = 0; i < 10; i++) 21 | { 22 | std::cin >> str; 23 | stream << str << std::flush; 24 | } 25 | 26 | return 0; 27 | } -------------------------------------------------------------------------------- /08-String & Stream/example/src/server.cpp: -------------------------------------------------------------------------------- 1 | #include "Socket.h" 2 | #include 3 | #include 4 | 5 | int main() 6 | { 7 | Network::Startup(); 8 | Network::Socket socket{ "127.0.0.1", 34567, Network::Socket::Tag::Listen }; 9 | if (!socket) 10 | { 11 | std::println("Listen socket error: {}", Network::GetErrorCode()); 12 | return 1; 13 | } 14 | Network::Socket acceptSock{ socket, Network::Socket::Tag::Accept }; 15 | if (!acceptSock) 16 | { 17 | std::println("Accept socket error: {}", Network::GetErrorCode()); 18 | return 1; 19 | } 20 | 21 | char str[1024]; 22 | while (true) 23 | { 24 | int len = ::recv(acceptSock.GetHandle(), str, 1024, 0); 25 | if (len <= 0) 26 | break; 27 | std::string_view content{ str, str + len }; 28 | std::println("Recv from server: {}", content); 29 | } 30 | return 0; 31 | } -------------------------------------------------------------------------------- /08-String & Stream/example/xmake.lua: -------------------------------------------------------------------------------- 1 | set_languages("cxxlatest") 2 | set_policy("build.warning", true) 3 | set_warnings("all") 4 | 5 | set_encodings("utf-8") 6 | 7 | add_rules("mode.debug", "mode.release") 8 | 9 | target("TCPStream") 10 | set_kind("static") 11 | add_files("src/Socket.cpp") 12 | 13 | target("server") 14 | add_deps("TCPStream") 15 | add_files("src/server.cpp") 16 | 17 | target("client") 18 | add_deps("TCPStream") 19 | add_files("src/client.cpp") -------------------------------------------------------------------------------- /08-String & Stream/现代C++基础 - 流与字符串.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/08-String & Stream/现代C++基础 - 流与字符串.pdf -------------------------------------------------------------------------------- /09-Move Semantics Basics/Answer.md: -------------------------------------------------------------------------------- 1 | # Answer 2 | 3 | 1. 只需要改变一句: 4 | 5 | ```c++ 6 | allRows.push_back(std::move(row)); 7 | ``` 8 | 9 | 这是因为注意到代码中`row`的值在这一句之后是没有再使用过的,因此转移到`vector`中时没有必要拷贝进去再把`row`析构,而是直接进行移动。但如果是下面的代码: 10 | 11 | ```c++ 12 | allRows.push_back(row); 13 | row += "blabla"; 14 | // row又做了什么操作 15 | ``` 16 | 17 | 那么显然就不能移动`row`了,因为我们后面还会用`row`的值,所以存入`vector`需要一个新的副本。换言之,后面不再需要的资源才可以转移,否则需要拷贝。 18 | 19 | 2. 代码如下: 20 | 21 | ```c++ 22 | template 23 | void swap(T& a, T& b) 24 | { 25 | T temp{ std::move(a) }; 26 | a = std::move(b); 27 | b = std::move(temp); 28 | } 29 | ``` 30 | 31 | 如果不用移动语义,就会出现三次拷贝,移动语义明显提升了性能。 32 | 33 | 3. 会输出10次`expensive delete`;改成逐成员交换就只输出1次。 34 | 35 | 可以注意到,事实上除了第一次的move真的释放了`ptr[0]`的资源,剩下的`ExpensiveDelete`全部都是对空的(也即被移动过的)对象的调用。因此,我们可以改为: 36 | 37 | ```c++ 38 | void ExpensiveDelete() 39 | { 40 | if (ptr == nullptr) 41 | return; 42 | std::println("expensive delete"); 43 | delete ptr; 44 | } 45 | ``` 46 | 47 | 就可以消除这个调用。在gcc -O3尝试一下,可以发现这种改进后,`erase`生成的汇编和逐成员交换是一致的。因此,如果析构对于空对象也是十分昂贵的,我们需要考虑加上空状态的判断来手动跳过这个部分。 48 | 49 | 4. 如下: 50 | 51 | ```c++ 52 | List(List&& another) noexcept 53 | { 54 | sentinel_.prev = std::exchange(another.sentinel_.prev, &another.sentinel_); 55 | sentinel_.next = std::exchange(another.sentinel_.next, &another.sentinel_); 56 | } 57 | 58 | List& operator=(List&& another) noexcept 59 | { 60 | if (this == &another) 61 | return *this; 62 | // 和答案中~ListBase()一样进行资源释放,这里省略了。 63 | ReleaseNodes(); 64 | 65 | sentinel_.prev = std::exchange(another.sentinel_.prev, &another.sentinel_); 66 | sentinel_.next = std::exchange(another.sentinel_.next, &another.sentinel_); 67 | return *this; 68 | } 69 | ``` 70 | 71 | 如果使用copy-and-swap idiom: 72 | 73 | ```c++ 74 | List& operator=(List&& another) noexcept 75 | { 76 | if (this == &another) 77 | return *this; 78 | 79 | List temp{ std::move(another) }; 80 | swap(temp); 81 | return *this; 82 | } 83 | ``` -------------------------------------------------------------------------------- /09-Move Semantics Basics/README.md: -------------------------------------------------------------------------------- 1 | # Assignment 2 | 3 | 1. 对以下代码片段,使用移动语义进行优化。 4 | 5 | ```c++ 6 | // 将文件按行划分,得到行的数组。 7 | std::ifstream fin{ "test.txt" }; 8 | std::vector allRows; 9 | std::string row; 10 | while (std::getline(fin, row)) { 11 | allRows.push_back(row); 12 | } 13 | ``` 14 | 15 | 2. 我们在C语言中是按如下方式对两个整数进行交换的: 16 | 17 | ```c++ 18 | void swap(int* ptr1, int* ptr2) 19 | { 20 | int temp = *ptr1; 21 | *ptr1 = *ptr2; 22 | *ptr2 = temp; 23 | } 24 | ``` 25 | 26 | 在C++中我们可以通过引用来增加一些可读性,也可以通过模板来定义一般的交换代码。请实现模板`swap`,注意考虑能否在其中使用移动语义。 27 | 28 | 3. 有一些人提出,如果移动赋值实现为逐成员交换可以在一些情况下里有较高的效率,例如我们擦除一个`vector`的第一个元素,那么事实上内部发生的是: 29 | 30 | ```c++ 31 | for (std::size_t i = 1; i < size; i++) 32 | ptr[i - 1] = std::move(ptr[i]); 33 | Destroy(ptr[size - 1]); 34 | size--; 35 | ``` 36 | 37 | 假如我们的类型如下: 38 | 39 | ```c++ 40 | class Foo 41 | { 42 | char *ptr = nullptr; 43 | void ExpensiveDelete() noexcept 44 | { 45 | std::println("expensive delete"); // 我们假设这里代表了比较昂贵的操作 46 | delete ptr; 47 | } 48 | 49 | public: 50 | ~Foo(){ ExpensiveDelete(); } 51 | 52 | Foo& operator=(Foo&& another) noexcept 53 | { 54 | if (this == &another) 55 | return *this; 56 | 57 | ExpensiveDelete(); 58 | ptr = std::exchange(another.ptr, nullptr); 59 | return *this; 60 | } 61 | }; 62 | ``` 63 | 64 | 对于如下代码: 65 | 66 | ```c++ 67 | std::vector vf(10); 68 | vf.erase(vf.begin()); 69 | ``` 70 | 71 | 会输出多少次`expensive delete`?如果我们将移动赋值函数改为逐成员交换,那么会输出多少次`expensive delete`?如果你想反驳这个理由,你应该在原来的代码基础上增加什么来达到相同的效果? 72 | 73 | 4. 给Error Handling作业中`List`增加移动构造函数和移动赋值函数;再使用copy-and-swap idiom改写。 74 | 75 | -------------------------------------------------------------------------------- /09-Move Semantics Basics/code/MyIntVector-sac.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | class MyIntVector 8 | { 9 | int *ptr_ = nullptr; 10 | std::size_t size_ = 0; 11 | 12 | public: 13 | MyIntVector(std::size_t initSize) 14 | { 15 | if (initSize == 0) 16 | return; 17 | ptr_ = new int[initSize], size_ = initSize; 18 | } 19 | int &operator[](std::size_t idx) { return ptr_[idx]; } 20 | int operator[](std::size_t idx) const { return ptr_[idx]; } 21 | auto size() const { return size_; } 22 | void swap(MyIntVector &another) noexcept 23 | { 24 | std::ranges::swap(ptr_, another.ptr_); 25 | std::ranges::swap(size_, another.size_); 26 | } 27 | 28 | MyIntVector(const MyIntVector &another) 29 | { 30 | if (another.size_ == 0) 31 | return; 32 | std::unique_ptr arr{ new int[another.size_] }; 33 | std::memcpy(arr.get(), another.ptr_, another.size_ * sizeof(int)); 34 | 35 | ptr_ = arr.release(); 36 | size_ = another.size_; 37 | return; 38 | } 39 | 40 | MyIntVector(MyIntVector &&another) noexcept 41 | : ptr_{ std::exchange(another.ptr_, nullptr) }, 42 | size_{ std::exchange(another.size_, 0) } 43 | { 44 | } 45 | 46 | MyIntVector &operator=(const MyIntVector &another) 47 | { 48 | if (this == &another) 49 | return *this; 50 | 51 | MyIntVector temp{ another }; 52 | swap(temp); 53 | return *this; 54 | } 55 | 56 | MyIntVector &operator=(MyIntVector &&another) noexcept 57 | { 58 | if (this == &another) 59 | return *this; 60 | 61 | MyIntVector temp{ std::move(another) }; 62 | swap(temp); 63 | return *this; 64 | } 65 | 66 | // If implemented as member-wise swap. 67 | // MyIntVector &operator=(MyIntVector &&another) noexcept 68 | // { 69 | // swap(another); 70 | // return *this; 71 | // } 72 | 73 | ~MyIntVector() { delete[] ptr_; } 74 | }; -------------------------------------------------------------------------------- /09-Move Semantics Basics/code/MyIntVector.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | class MyIntVector 7 | { 8 | int *ptr_ = nullptr; 9 | std::size_t size_ = 0; 10 | 11 | void Clear_() noexcept { delete[] ptr_; } 12 | 13 | void Reset_() noexcept { ptr_ = nullptr, size_ = 0; } 14 | 15 | // This method assumes currently ptr_ = nullptr && size_ = 0. 16 | void AllocAndCopy_(const MyIntVector &another) 17 | { 18 | if (another.size_ == 0) 19 | return; 20 | std::unique_ptr arr{ new int[another.size_] }; 21 | std::memcpy(arr.get(), another.ptr_, another.size_ * sizeof(int)); 22 | 23 | ptr_ = arr.release(); 24 | size_ = another.size_; 25 | return; 26 | } 27 | 28 | public: 29 | MyIntVector(std::size_t initSize) 30 | { 31 | if (initSize == 0) 32 | return; 33 | ptr_ = new int[initSize], size_ = initSize; 34 | } 35 | int &operator[](std::size_t idx) { return ptr_[idx]; } 36 | int operator[](std::size_t idx) const { return ptr_[idx]; } 37 | auto size() const { return size_; } 38 | 39 | MyIntVector(const MyIntVector &another) { AllocAndCopy_(another); } 40 | 41 | MyIntVector(MyIntVector &&another) noexcept 42 | : ptr_{ std::exchange(another.ptr_, nullptr) }, 43 | size_{ std::exchange(another.size_, 0) } 44 | { 45 | } 46 | 47 | MyIntVector &operator=(const MyIntVector &another) 48 | { 49 | if (this == &another) 50 | return *this; 51 | 52 | Clear_(); 53 | Reset_(); // For basic exception guarantee. 54 | AllocAndCopy_(another); 55 | return *this; 56 | } 57 | 58 | MyIntVector &operator=(MyIntVector &&another) noexcept 59 | { 60 | if (this == &another) 61 | return *this; 62 | 63 | Clear_(); 64 | ptr_ = std::exchange(another.ptr_, nullptr), 65 | size_ = std::exchange(another.size_, 0); 66 | } 67 | 68 | ~MyIntVector() { Clear_(); } 69 | }; -------------------------------------------------------------------------------- /09-Move Semantics Basics/现代C++基础 - Move Semantics Basics.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/09-Move Semantics Basics/现代C++基础 - Move Semantics Basics.pdf -------------------------------------------------------------------------------- /10-Value Category and Move Semantics/Answer.md: -------------------------------------------------------------------------------- 1 | # Answer 2 | 3 | 1. ```c++ 4 | template 5 | T exchange(T& a, const T& b) 6 | { 7 | T temp{ std::move(a) }; 8 | a = b; 9 | return temp; // NRVO 10 | } 11 | ``` 12 | 13 | 2. 显然不可以,因为用值类型替换重载的前提是已经定义好了相应的构造函数;如果我们进行这种替换,则会出现递归调用。 14 | 15 | 假设构造函数正常编写了;对于赋值函数,我们可以修改一句来使其正确,代码如下: 16 | 17 | ```c++ 18 | class A 19 | { 20 | public: 21 | A& operator=(A obj) { 22 | swap(*this, obj); 23 | return *this; 24 | } 25 | }; 26 | ``` 27 | 28 | 这本质上就是不进行自赋值判断的copy-and-swap idiom(当然,自赋值在这个idiom里不会引起致命错误,只会导致不提前返回,运行效率有所下降)。如果有些人这样编写赋值函数也不要太惊讶。 -------------------------------------------------------------------------------- /10-Value Category and Move Semantics/README.md: -------------------------------------------------------------------------------- 1 | # Assignment 2 | 3 | 1. 实现`exchange`,功能与`std::exchange`相同,注意利用移动语义。假设第二个参数是使用`const&`直接传递的(我们在下一节的作业中会改进这一点)。 4 | 5 | 2. 小明发现,构造函数和赋值函数都是具有两个重载的(`const& + &&`),而我们课上又说过可以用值类型来替换重载,那么可否可以进行如下的化简?说明理由,思考其中实际发生了什么。 6 | 7 | ```c++ 8 | class A 9 | { 10 | A(A obj) { *this = std::move(obj); } 11 | A& operator=(A obj) { *this = std::move(obj); } 12 | }; 13 | ``` 14 | 15 | 其中一句可以进行修改来解决问题,思考如何进行(提示: swap)。这时它等价于什么? -------------------------------------------------------------------------------- /10-Value Category and Move Semantics/code/RVO and NRVO.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | class Object 3 | { 4 | public: 5 | Object() { std::cout << "Construct at " << this << "\n"; }; 6 | ~Object() { std::cout << "Destruct at " << this << "\n"; }; 7 | Object(const Object&) { 8 | std::cout << "Const Copy at " << this << "\n"; 9 | }; 10 | Object(Object&&) { std::cout << "Move at " << this << "\n"; }; 11 | Object& operator=(const Object&) { 12 | std::cout << "Const Copy Assignment at " << this << "\n"; 13 | return *this; 14 | }; 15 | Object& operator=(Object&&) { 16 | std::cout << "Move Assignment at " << this << "\n"; 17 | return *this; 18 | }; 19 | }; 20 | 21 | Object GetObject_RVO() 22 | { 23 | return Object(); 24 | } 25 | 26 | Object GetObject_NRVO() 27 | { 28 | Object obj; 29 | return obj; 30 | } 31 | 32 | int main() 33 | { 34 | std::cout << std::hex; 35 | 36 | std::cout << "RVO\n"; 37 | Object obj1; 38 | obj1 = GetObject_RVO(); 39 | 40 | std::cout << "\nNRVO\n"; 41 | Object obj2; 42 | obj2 = GetObject_NRVO(); 43 | 44 | std::cout << "\nDone.\n"; 45 | return 0; 46 | } -------------------------------------------------------------------------------- /10-Value Category and Move Semantics/现代C++基础 - 值分类与移动语义.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/10-Value Category and Move Semantics/现代C++基础 - 值分类与移动语义.pdf -------------------------------------------------------------------------------- /11-Template Basics and Move Semantics/Answer.md: -------------------------------------------------------------------------------- 1 | # Answer 2 | 3 | ## Templates 4 | 5 | ### Further Reading 6 | 7 | 3. 前两个不正确,最后一个正确。如果想间接返回容器,需要用`std::array`。或许有人想这么写: 8 | 9 | ```c++ 10 | constexpr auto ToArray(auto& rng) 11 | { 12 | auto vec = ToVector(rng); 13 | 14 | using ElemType = std::ranges::range_value_t; 15 | constexpr std::size_t size = vec.size(); 16 | std::array arr; 17 | std::ranges::copy(vec, arr.begin()); 18 | return arr; 19 | } 20 | ``` 21 | 22 | 然而`constexpr std::size_t size = vec.size()`是错误的,因为`vec`本身不是`constexpr`变量。可以用下面的方式绕过去: 23 | 24 | ```c++ 25 | constexpr auto ToArray(auto& rng) 26 | { 27 | // 在这个context中,内部的vector已经析构,我们没有保留下来,所以不违反要求。 28 | constexpr std::size_t size = ToVector(rng).size(); 29 | 30 | using ElemType = std::ranges::range_value_t; 31 | std::array arr; 32 | std::ranges::copy(ToVector(rng), arr.begin()); 33 | return arr; 34 | } 35 | ``` 36 | 37 | 当然这要求`rng`本身不会因为遍历而修改。 38 | 39 | 4. `foo`可以,`foo2`不行,因为`foo2`内的`v`开启了新的context,在这个context中的异常没有被catch。 40 | 41 | ### Part 1 42 | 43 | 1. gcc默认允许VLA。 44 | 45 | 2. ```c++ 46 | template 47 | struct is_same 48 | { 49 | static inline constexpr bool value = false; 50 | }; 51 | 52 | template 53 | struct is_same 54 | { 55 | static inline constexpr bool value = true; 56 | }; 57 | 58 | template 59 | inline constexpr bool is_same_v = is_same::value; 60 | ``` 61 | 62 | 3. 可以使用constexpr if: 63 | 64 | ```c++ 65 | template 66 | constexpr int Foo() 67 | { 68 | if constexpr (N % 2 == 0) { 69 | return N / 2; 70 | } 71 | else { 72 | return 3 * N + 1; 73 | } 74 | } 75 | ``` 76 | 77 | 也可以使用特化: 78 | 79 | ```c++ 80 | template 81 | struct Foo { }; 82 | 83 | template 84 | struct Foo 85 | { 86 | static inline constexpr unsigned int value = N / 2; 87 | }; 88 | 89 | template 90 | struct Foo 91 | { 92 | static inline constexpr unsigned int value = 3 * N + 1; 93 | }; 94 | 95 | template 96 | inline constexpr unsigned int Foo_v = Foo::value; 97 | ``` 98 | 99 | 100 | ## Part 2 101 | 102 | 1. 不合法,虽然逻辑上讲二者是一样的(德摩根定律),但是`!A`和`!B`会被看作Unknown1和Unknown2,而`!(C || D)`则是Unknown3,前者的`&&`并不和后者构成subsume关系。 103 | 104 | 2. 当然可以很容易地通过特化来完成: 105 | 106 | ```c++ 107 | template 108 | void Insert(T& cont, const U& val) 109 | { 110 | cont.insert(val); 111 | } 112 | 113 | template 114 | requires requires(T cont, U val) { cont.push_back(val); } 115 | void Insert(T& cont, const U& val) 116 | { 117 | cont.push_back(val); 118 | } 119 | ``` 120 | 121 | > 当然,你也可以把`U`写成`std::ranges::iter_value_t<>`,少写一个模板参数: 122 | > 123 | > ```c++ 124 | > template 125 | > requires requires(T cont, std::ranges::range_value_t val) 126 | > { cont.push_back(val); } 127 | > void Insert(T& cont, const std::ranges::range_value_t& val) 128 | > { 129 | > cont.push_back(val); 130 | > } 131 | > ``` 132 | > 133 | > 当然写两遍参数感觉非常蠢(,所以补充一点:requires clause是可以写在函数头后面的。这样就可以写成下面的形式: 134 | > 135 | > ```c++ 136 | > template 137 | > void Insert(T& cont, const std::ranges::range_value_t& val) 138 | > requires requires { cont.push_back(val); } 139 | > { 140 | > cont.push_back(val); 141 | > } 142 | > ``` 143 | 144 | 如果使用constexpr if,可以利用requires expression可以转化为bool的性质: 145 | 146 | ```c++ 147 | template 148 | void Insert(T& cont, const U& val) 149 | { 150 | if constexpr (requires { cont.push_back(val); }) { 151 | cont.push_back(val); 152 | } 153 | else { 154 | cont.insert(val); 155 | } 156 | } 157 | ``` 158 | 159 | 当然,你也可以利用concept本身可以转化为bool的性质: 160 | 161 | ```c++ 162 | template 163 | concept SupportPushBack = requires(T cont, U val) { cont.push_back(val); }; 164 | 165 | template 166 | void Insert(T& cont, const U& val) 167 | { 168 | if constexpr (SupportPushBack) { 169 | cont.push_back(val); 170 | } 171 | else { 172 | cont.insert(val); 173 | } 174 | } 175 | ``` 176 | 177 | 3. ```c++ 178 | void Func(std::convertible_to auto a) {} 179 | ``` 180 | 181 | 4. ```c++ 182 | template 183 | decltype(auto) MoveIfNoexcept(T& arg) 184 | { 185 | if constexpr (std::is_nothrow_move_constructible_v || 186 | !std::is_copy_constructible_v) 187 | { 188 | return std::move(arg); 189 | } 190 | else { 191 | return static_cast(arg); 192 | } 193 | } 194 | ``` 195 | 196 | 当然,`else`里也可以使用C++17的`std::as_const`,它接受`T&`,返回`const T&`: 197 | 198 | ```c++ 199 | return std::as_const(arg); 200 | ``` 201 | 202 | 在C++17之前,你也可以使用`std::add_const_t`的type traits。 203 | 204 | 5. `b.Test()`和`A c`不能编译通过,其他可以。尽管`long`不满足`Test`的约束,但是仍然可以实例化`A`,只是不能调用`Test`方法。 205 | 206 | ## Move Semantics 207 | 208 | 1. 先不考虑`const`,我们可以很容易写出来代码: 209 | 210 | ```c++ 211 | template 212 | auto&& forward_like(U&& arg) 213 | { 214 | if constexpr (std::is_lvalue_reference_v) { 215 | return arg; 216 | } 217 | else { 218 | return std::move(arg); 219 | } 220 | } 221 | ``` 222 | 223 | 然后每个分支加上`const`的判断就可以了: 224 | 225 | ```c++ 226 | template 227 | auto&& forward_like(U&& arg) 228 | { 229 | static constexpr bool is_const = std::is_const_v>; 230 | if constexpr (std::is_lvalue_reference_v) { 231 | if constexpr (is_const) { 232 | return std::as_const(arg); 233 | } 234 | else { 235 | return arg; 236 | } 237 | } 238 | else { 239 | if constexpr (is_const) { 240 | return std::move(std::as_const(arg)); 241 | } 242 | else { 243 | return std::move(arg); 244 | } 245 | } 246 | } 247 | ``` 248 | 249 | 1. 代码如下: 250 | 251 | ```c++ 252 | decltype(auto) call(auto&& func, auto&& param) 253 | requires std::invocable // 当然不加这句也行。 254 | { 255 | return func(std::forward(param)); 256 | } 257 | ``` 258 | 259 | 注意完美转发和使用`decltype(auto)`,后者不能使用`auto&&`,因为如果`func`返回值类型,万能引用会把返回值类型变为右值引用,和原函数不一致。 260 | 261 | > 注意就算`func`的返回类型是`void`也可以写在`return`里,返回类型也会推断为`void`。 262 | 263 | + 不完全一致,假设有如下函数: 264 | 265 | ```c++ 266 | void Func(C val); 267 | Func(C{}); 268 | ``` 269 | 270 | 我们知道,由于prvalue的copy elision,只会调用一次构造函数。但是如果使用上述调用,则相当于: 271 | 272 | ```c++ 273 | void call(C&& val) 274 | { 275 | return Func(std::move(val)); 276 | } 277 | ``` 278 | 279 | prvalue遇到`C&&`会进行materialize,调用构造函数;随后传给`Func`时,构造`C`就会调用移动构造函数,产生了额外开销。 280 | 281 | > 换句话说,尽管有了完美引用,但是C++仍然不能在理论上全无代价地传递值类型参数。不过编译器可能可以在没有副作用时优化掉这次移动。 282 | 283 | + 我们先说C++23的情况,这个写法就非常简单了: 284 | 285 | ```c++ 286 | decltype(auto) call(auto&& func, auto&& param) 287 | { 288 | decltype(auto) result = func(std::forward(param)); 289 | // do something on result... 290 | return result; 291 | } 292 | ``` 293 | 294 | 我们讨论一下: 295 | 296 | + 如果函数返回值类型,则`decltype(auto)`推断出值类型,并发生NRVO; 297 | + 如果函数返回左值引用,则`decltype(auto)`推断出左值引用,也正常返回这个引用; 298 | + 如果函数返回右值引用,则`decltype(auto)`推断出右值引用;虽然`result`这个右值引用是左值,但是`return`时会视作xvalue,仍然可以正常返回对应的右值引用。 299 | 300 | > 注意,我们课上说过。视作`xvalue`并不影响返回值类型的类型推断,因为`result`是一个变量,对它的推导就是原本的类型。因此,`return result;`并不会推断出右值引用。但是,如果是`return (result);`,则由于后者是表达式,并规定产生xvalue,于是推断出右值引用。 301 | > 302 | 303 | 对于C++23之前,可能有人是这么写的: 304 | 305 | ```c++ 306 | decltype(auto) call(auto&& func, auto&& param) 307 | { 308 | decltype(auto) result = func(std::forward(param)); 309 | return static_cast(result); 310 | } 311 | ``` 312 | 313 | 注意,虽然直接`return result;`推断的返回类型是正确的,但是当`result`是右值引用时,由于`result`是左值,不能直接绑定右值引用,因此需要这个转型。但是,上述写法还有一个问题:对于值类型,`return result;`是可以NRVO的,但`static_cast`由于不是name,因此不能进行NRVO,甚至不能进行implicit cast,于是**多了一次拷贝**。解决方案就是使用`if constexpr`: 314 | 315 | ```c++ 316 | decltype(auto) call(auto&& func, auto&& param) 317 | { 318 | decltype(auto) result = func(std::forward(param)); 319 | using T = decltype(result); 320 | if constexpr (!std::is_reference_v) { 321 | return result; 322 | } 323 | else { 324 | return static_cast(result); 325 | } 326 | } 327 | ``` 328 | 329 | 或者由于只有右值引用需要转型,也可以写成: 330 | 331 | ```c++ 332 | decltype(auto) call(auto&& func, auto&& param) 333 | { 334 | decltype(auto) result = func(std::forward(param)); 335 | using T = decltype(result); 336 | if constexpr (std::is_rvalue_reference_v) { 337 | return std::move(result); 338 | } 339 | else { 340 | return result; 341 | } 342 | } 343 | ``` 344 | 345 | 346 | 347 | -------------------------------------------------------------------------------- /11-Template Basics and Move Semantics/README.md: -------------------------------------------------------------------------------- 1 | # Assignment 2 | 3 | ## Templates 4 | 5 | ### Further Reading: Core constant expressions 6 | 7 | 由于constexpr函数中允许非编译期可求值的表达式出现(因为允许运行时求值),因此尽管其条件非常放松,但不代表可出现在其中的表达式都可以编译期求值。 8 | 9 | 不过,我们没有仔细地探讨哪些表达式是可以编译期求值的,因为大部分都比较符合直觉(对于比较完整的规则,可见[cppreference](https://en.cppreference.com/w/cpp/language/constant_expression)或者[C++标准](https://eel.is/c++draft/expr.const#10));我们下面讨论一些比较特殊的情况。 10 | 11 | 1. 虚函数:从C++20开始,虚函数才被允许是constexpr的,只要可以编译期确定实际类型,就能够正常地调用相应函数。 12 | 13 | ```c++ 14 | class Base 15 | { 16 | public: 17 | // 从C++20开始才允许virtual constexpr。 18 | virtual constexpr int func() const { return 1; } 19 | }; 20 | 21 | class Derived : public Base 22 | { 23 | public: 24 | constexpr int func() const override { return 2; } 25 | }; 26 | ``` 27 | 28 | 例如: 29 | 30 | ```c++ 31 | constexpr int Test(const Base& obj) 32 | { 33 | return obj.func(); 34 | } 35 | 36 | int main() 37 | { 38 | constexpr Base obj; 39 | static_assert(Test(obj) == 1); 40 | 41 | constexpr Derived obj2; 42 | static_assert(Test(obj2) == 2); 43 | 44 | return 0; 45 | } 46 | ``` 47 | 48 | 2. RTTI:从C++20开始,`dynamic_cast`和`typeid`变为编译期可求值的表达式(和虚函数要求相同,需要编译期知道对应的object是什么类型);`typeinfo`的`operator==`从C++20开始也变成constexpr函数了。 49 | 50 | ```c++ 51 | constexpr int Test2(const Base& obj) 52 | { 53 | if (dynamic_cast(&obj) == nullptr) { 54 | return 3; 55 | } 56 | return 4; 57 | } 58 | 59 | constexpr int Test3(const Base& obj) 60 | { 61 | if (typeid(obj) == typeid(Derived)) { 62 | return 3; 63 | } 64 | return 4; 65 | } 66 | ``` 67 | 68 | 3. 堆内存的分配:new expression被视作不可编译期求值的,但C++20进行了一定的放松;简单来说,new出来的变量必须在相同的编译期context进行delete(常称为"transient allocation")。相应地,C++20也进行了如下改进: 69 | 70 | + 使`vector`和`string`的构造函数及成员函数变为`constexpr`的(C++23又加入了`unique_ptr`); 71 | + 允许析构函数是`constexpr`的(之前不能手动标注,只有trivial destructor默认`constexpr`)。 72 | + 使``、``和``中一大批函数都变为`constexpr`。 73 | 74 | 可能比较抽象,我们举一个最简单的例子: 75 | 76 | ```c++ 77 | constexpr int Test() { 78 | std::vector v{ 1,2,3 }; 79 | return v.size(); 80 | } 81 | constexpr int size = Test(); // Okay 82 | 83 | constexpr int Test2() { 84 | constexpr std::vector v{ 1,2,3 }; // Error 85 | return v.size(); 86 | } 87 | constexpr int size = Test2(); 88 | ``` 89 | 90 | 可以这么理解:每遇到一个必须要求编译期求值的位置(例如`constexpr`变量、数组大小等),就进入了新的context,此时内部所有语句都必须满足编译期可求值的要求。 91 | 92 | + 对于`size = Test()`进入的环境,由于`v`析构时会delete,所以满足了前面说的new的放松条件; 93 | + 对于`size = Test2()`,注意到`Test2`中`v`是一个constexpr变量,因此开启了新的context,需要探查其内部本身是否满足条件。而构造函数内部不会进行delete,因此不再满足放松条件。 94 | 95 | 再举一个例子: 96 | 97 | ```c++ 98 | template 99 | constexpr auto MyAverage(T& rng) // 先插入一个默认值,再求平均 100 | { 101 | using ElemType = std::ranges::range_value_t; 102 | std::vector v{ std::ranges::begin(rng), std::ranges::end(rng) }; 103 | v.push_back(ElemType{}); 104 | std::ranges::sort(v); 105 | auto newEnd = std::unique(v.begin(), v.end()); 106 | auto sum = std::accumulate(v.begin(), newEnd, ElemType{}); 107 | return sum / static_cast(v.size()); 108 | } 109 | ``` 110 | 111 | 思考,下面的代码是正确的吗? 112 | 113 | ```c++ 114 | template 115 | constexpr auto ToVector(T& rng) 116 | { 117 | using ElemType = std::ranges::range_value_t; 118 | std::vector v{ std::ranges::begin(rng), std::ranges::end(rng) }; 119 | return v; 120 | } 121 | 122 | constexpr std::array arr{ 1, 2, 3 }; 123 | constexpr std::vector v{ ToVector(arr) }; // 正确吗? 124 | constexpr std::vector v2{0, 8, 15, 132, 4, 77}; // 正确吗? 125 | 126 | constexpr int MySize(T& rng) { 127 | auto v = ToVector(rng); 128 | return v.size(); 129 | } 130 | constexpr int v2 = MySize(arr); // 正确吗? 131 | ``` 132 | 133 | 思考有没有什么方式绕过去,间接返回一个容器? 134 | 135 | > 关于编译期的allocation,Barry Revzin写了一篇文章非常详尽地描述其困境:[What's so hard about constexpr allocation? | Barry's C++ Blog](https://brevzin.github.io/c++/2024/07/24/constexpr-alloc/),预计很长时间内这一方面的进展都会比较缓慢。 136 | 137 | 4. 异常:throw表达式是不可编译期求值的;C++20开始允许进行try-catch,但是仍然不允许执行流遇到异常。从C++26开始又进行了放松,即只要在context内异常被catch,就不会导致编译错误,仿佛正常抛出了异常。 138 | 139 | ```c++ 140 | constexpr std::optional Access(const std::array& arr, int idx) { 141 | try { 142 | return arr.at(3); 143 | } catch(const std::exception& ex) { 144 | return std::nullopt; 145 | } 146 | } 147 | // C++26前由于遇到throw而直接导致无法编译期求值,C++26之后则正常返回。 148 | static_assert(Access({ 1,2,3 }, 4) == std::nullopt); 149 | ``` 150 | 151 | 思考一下下面的`foo`和`foo2`是否可以编译通过。 152 | 153 | ```c++ 154 | constexpr int just_error() { 155 | throw my_exception{}; 156 | return 1; 157 | } 158 | 159 | constexpr void foo() { 160 | try { 161 | auto v = just_error(); 162 | } catch (my_exception) { } 163 | } 164 | 165 | constexpr void foo2() { 166 | try { 167 | constexpr auto v = just_error(); 168 | } catch (my_exception) { } 169 | } 170 | ``` 171 | 172 | 5. UB:例如整数除以0,编译期eval会编译报错。 173 | 174 | 6. `reinterpret_cast`:不允许。 175 | 176 | 7. 对context外变量的修改:不允许。例如: 177 | 178 | ```c++ 179 | constexpr int Inc(int& n) { return ++n; } 180 | constexpr int Test() { 181 | int n = 1; 182 | constexpr int m = Inc(n); // compile error. 183 | int l = Inc(n); // ok,因为没有开启一个新的context,修改操作不在context外。 184 | } 185 | ``` 186 | 187 | 除了这些之外,还有一些比较小的改进,比如C++23引入了constexpr bitset,C++26引入了constexpr structured binding以及constexpr placement new(限制分配位置只能是变量地址)。 188 | 189 | > 特别地,这种“语句可以出现在编译期进行求值”,和“值本身是编译期求得”的差别,导致了如下的代码是错误的: 190 | > 191 | > ```c++ 192 | > template 193 | > constexpr void Test() 194 | > { 195 | > for (int i = 0; i < N; i++) { 196 | > std::array arr; // WRONG 197 | > } 198 | > } 199 | > ``` 200 | > 201 | > `i`不是`constexpr`变量,不能出现在模板参数的位置。 202 | 203 | ### Part 1 204 | 205 | 1. 小明写下如下程序: 206 | 207 | ```c++ 208 | int main() 209 | { 210 | static constinit int size = 3; 211 | int arr[size]; 212 | } 213 | ``` 214 | 215 | 他发现在gcc上编译可以通过,于是开始质疑讲的有问题。思考一下为什么能编译通过。 216 | 217 | 2. 利用特化实现`std::is_same_v`。 218 | 219 | 3. 用两种方法,编译期完成如下任务:当一个自然数$n$是奇数,返回$3n+1$;否则返回$n/2$。 220 | 221 | ### Part 2 222 | 223 | 1. 判断以下代码是否会导致编译错误(`integral`和`same_as`都是``中定义的标准库concept): 224 | 225 | ```c++ 226 | #include 227 | 228 | template 229 | requires (!std::integral) && (!std::same_as) 230 | void Func(T x) {} 231 | 232 | template 233 | requires !(std::integral || std::same_as) 234 | void Func(T x) {} 235 | 236 | int main() 237 | { 238 | Func(1.0); 239 | } 240 | ``` 241 | 242 | 2. 写一个`Insert`函数,接受参数`x`和`val`,如果`x`可以调用`push_back`则调用并插入`val`,否则调用`insert`。你能想到如何使用constexpr if来完成吗? 243 | 244 | 3. 写一个函数,要求其参数可以转换为`int`。阅读`std::convertible_to`这个concept的文档,用缩写的方式(即无requires clause)给出答案。 245 | 246 | 4. 实现`std::move_if_noexcept`,当``的移动构造不会抛异常时,或者无拷贝构造函数时,进行`std::move`;否则返回`const&`。这个函数通常用来保证异常安全性。 247 | 248 | 5. 补充一个知识,模板类成员函数可以对其模板参数施加约束,**当约束不满足时不会导致编译错误,只会去掉这个成员函数**。判断以下代码中哪些语句不能够编译通过: 249 | 250 | ```c++ 251 | template 252 | class A 253 | { 254 | public: 255 | bool Test() 256 | requires std::same_as 257 | { 258 | return true; 259 | } 260 | }; 261 | 262 | int main() 263 | { 264 | A a; 265 | a.Test(); 266 | 267 | A b; 268 | b.Test(); 269 | 270 | A c; 271 | } 272 | ``` 273 | 274 | ## Move Semantics 275 | 276 | 1. 在C++23引入了新函数`forward_like`,它的作用是“像`forward(x)`一样`forward(y)`”。例如我们举过的`optional`的`value`的例子: 277 | 278 | ```c++ 279 | decltype(auto) value(this auto&& self) { 280 | return std::forward(self).value_; 281 | } 282 | ``` 283 | 284 | 也可以利用`forward_like`写为: 285 | 286 | ```c++ 287 | decltype(auto) value(this auto&& self) { 288 | return std::forward_like(self.value_); 289 | } 290 | ``` 291 | 292 | 从而当`self`为左值时,会以左值的方式forward `self.value_`(也就是什么都不干);当`self`为右值时,会以右值的方式forward `self.value_`(也就是进行移动)。 293 | 294 | 请实现`forward_like`;特别地,这个函数在模板参数为`const`时,这种`const`也会传递给函数参数。例如上式中,若`decltype(self)`为`const&`,则`self.value_`也会转为`const Value&`。在成员访问中这是多此一举(因为const变量的成员只能是const),但是在其他情况未必。 295 | 296 | > 你可以用`std::is_const_v>`来判断一个引用是不是`const&`。**特别注意,`std::is_const_v`对`T=const&`推断出false,必须先把引用去掉再判断。** 297 | 298 | 2. 写一个函数`call`,它接受一个可调用对象`func`和一个参数`param`,用一行代码原封不动返回`func`传入`param`的调用结果并返回(返回类型与`call`的返回类型相同),并回答以下问题: 299 | 300 | + 这个调用和直接从caller调用`func`的效果完全一致吗?是否会造成额外开销?(Hint: prvalue) 301 | + 如果需要对调用结果进行一定的操作再返回,即需要将调用结果存入一个变量,那么`return`应当怎么写?分别讨论C++23之前/之后的情况。 -------------------------------------------------------------------------------- /11-Template Basics and Move Semantics/现代C++基础 - 模板基础与移动语义.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/11-Template Basics and Move Semantics/现代C++基础 - 模板基础与移动语义.pdf -------------------------------------------------------------------------------- /12-Advanced Template/Answer-code/FixedString.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | template 7 | class BasicFixedString 8 | { 9 | constexpr BasicFixedString() = default; 10 | 11 | public: 12 | CharType str_[N]; // 必须是public的,否则不是structural type。 13 | 14 | // 能乐意写这么多constexpr的都是神人,我是真的写烦了。。 15 | constexpr BasicFixedString(const CharType (&arr)[N + 1]) 16 | { 17 | std::copy(arr, arr + N, str_); 18 | } 19 | constexpr std::size_t size() const noexcept { return N; } 20 | constexpr const CharType *c_str() const noexcept { return str_; } 21 | constexpr const CharType *data() const noexcept { return str_; } 22 | constexpr CharType *data() noexcept { return str_; } 23 | // 正常来说begin也得写const + non-const两个版本(分别对应iterator和const 24 | // iterator),但是可以用deducing this简化。 25 | constexpr auto begin(this auto &&self) noexcept { return self.data(); } 26 | constexpr auto end(this auto &&self) noexcept { return self.data() + N; } 27 | constexpr std::basic_string_view view() const noexcept 28 | { 29 | return { begin(), end() }; 30 | } 31 | 32 | template 33 | friend constexpr BasicFixedString operator+( 34 | const BasicFixedString &, 35 | const BasicFixedString &); 36 | }; 37 | 38 | // 顺便加了一个N == 0的特化,数组大小为0是非法的(虽然gcc允许),但是 39 | // std::array是合法的(因为标准库里进行了特化)。 40 | template 41 | class BasicFixedString 42 | { 43 | public: 44 | std::array str_; 45 | 46 | constexpr BasicFixedString(const CharType (&arr)[1]) {} 47 | constexpr std::size_t size() const noexcept { return 0; } 48 | constexpr const CharType *c_str() const noexcept { return str_.data(); } 49 | constexpr auto data(this auto &&self) noexcept { return self.str_.data(); } 50 | constexpr auto begin(this auto &&self) noexcept { return self.data(); } 51 | constexpr auto end(this auto &&self) noexcept { return self.data(); } 52 | constexpr std::basic_string_view view() const noexcept 53 | { 54 | return { begin(), end() }; 55 | } 56 | 57 | template 58 | friend constexpr BasicFixedString operator+( 59 | const BasicFixedString &, 60 | const BasicFixedString &); 61 | }; 62 | 63 | template 64 | BasicFixedString(const CharType (&arr)[N]) -> BasicFixedString; 65 | 66 | template 67 | constexpr BasicFixedString operator+( 68 | const BasicFixedString &a, 69 | const BasicFixedString &b) 70 | { 71 | BasicFixedString result; 72 | auto dst = result.data(); 73 | std::ranges::copy(a.str_, dst); 74 | std::ranges::copy(b.str_, dst + N1); 75 | return result; 76 | } 77 | 78 | template 79 | using FixedString = BasicFixedString; 80 | 81 | template 82 | void PrintStr() 83 | { 84 | std::println("{}", str.view()); 85 | } 86 | 87 | int main() 88 | { 89 | PrintStr<"123">(); 90 | // 注意a和b是相同类型的(size == 3),才能一起CTAD;否则要写两行。 91 | constexpr FixedString a{ "456" }, b{ "789" }; 92 | PrintStr(); 93 | 94 | constexpr FixedString c{ "" }; 95 | PrintStr(); // 测试特化的N == 0。 96 | PrintStr(); 97 | 98 | return 0; 99 | } -------------------------------------------------------------------------------- /12-Advanced Template/Answer-code/Function.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | template 8 | class Function; 9 | 10 | namespace Detail 11 | { 12 | template 13 | T *AllocFunction(Args &&...args) 14 | { 15 | return new T{ std::forward(args)... }; 16 | } 17 | 18 | template 19 | concept GeneralFunctionMatch = 20 | std::convertible_to, Args...>, 21 | ReturnType>; 22 | 23 | template 24 | struct IsFunctionInstantiation : std::false_type 25 | { 26 | }; 27 | 28 | template 29 | struct IsFunctionInstantiation> : std::true_type 30 | { 31 | }; 32 | 33 | } // namespace Detail 34 | 35 | template 36 | class FunctionProxyBase 37 | { 38 | public: 39 | virtual FunctionProxyBase *Clone() const = 0; 40 | virtual ReturnType Call(Args...) = 0; 41 | virtual const std::type_info &TargetType() const noexcept = 0; 42 | virtual ~FunctionProxyBase() = default; 43 | }; 44 | 45 | template 46 | class FunctionProxy : public FunctionProxyBase 47 | { 48 | T func_; 49 | 50 | public: 51 | FunctionProxy(const T &func) : func_{ func } {} 52 | FunctionProxy(T &&func) : func_{ std::move(func) } {} 53 | 54 | FunctionProxy *Clone() const override 55 | { 56 | return Detail::AllocFunction(func_); 57 | } 58 | ReturnType Call(Args... args) override 59 | { 60 | return std::invoke(func_, std::forward(args)...); 61 | } 62 | const std::type_info &TargetType() const noexcept override 63 | { 64 | return typeid(T); 65 | } 66 | }; 67 | 68 | template 69 | class Function 70 | { 71 | using ProxyBasePtr = FunctionProxyBase *; 72 | 73 | ProxyBasePtr proxy_ = nullptr; 74 | void Dealloc_() noexcept { delete GetImplPtr_(); } 75 | 76 | public: 77 | Function() noexcept = default; 78 | Function(std::nullptr_t) noexcept {} 79 | Function(const Function &another) 80 | : proxy_{ another ? another.GetImplPtr_()->Clone() : nullptr } 81 | { 82 | } 83 | Function(Function &&another) 84 | : proxy_{ std::exchange(another.GetImplPtr_(), nullptr) } 85 | { 86 | } 87 | 88 | Function &operator=(const Function &another) 89 | { 90 | Function{ another }.swap(*this); 91 | return *this; 92 | } 93 | Function &operator=(Function &&another) noexcept 94 | { 95 | Dealloc_(); 96 | GetImplPtr_() = std::exchange(another.GetImplPtr_(), nullptr); 97 | return *this; 98 | } 99 | Function &operator=(std::nullptr_t) noexcept 100 | { 101 | Dealloc_(); 102 | GetImplPtr_() = nullptr; 103 | return *this; 104 | } 105 | template F0> 106 | Function &operator=(F0 &&f) 107 | { 108 | Function{ std::forward(f) }.swap(*this); 109 | return *this; 110 | } 111 | template 112 | Function &operator=(std::reference_wrapper f) noexcept 113 | { 114 | Function{ f }.swap(*this); 115 | return *this; 116 | } 117 | 118 | ~Function() { Dealloc_(); } 119 | 120 | ProxyBasePtr &GetImplPtr_() noexcept { return proxy_; } 121 | ProxyBasePtr GetImplPtr_() const noexcept { return proxy_; } 122 | 123 | template F0> 124 | requires(!std::same_as, 125 | Function>) // Required since C++23 126 | Function(F0 &&f) 127 | { 128 | using F = std::decay_t; 129 | // Required since C++23 130 | static_assert(std::is_copy_constructible_v && 131 | std::is_constructible_v); 132 | 133 | // Callable pointer is definitely function pointer. 134 | if constexpr (std::is_pointer_v || 135 | std::is_member_function_pointer_v || 136 | Detail::IsFunctionInstantiation::value) 137 | { 138 | if (f == nullptr) 139 | return; 140 | } 141 | 142 | GetImplPtr_() = 143 | Detail::AllocFunction>( 144 | std::forward(f)); 145 | } 146 | 147 | ReturnType operator()(Args... args) const 148 | { 149 | if (!*this) 150 | { 151 | throw std::bad_function_call{}; 152 | } 153 | return GetImplPtr_()->Call(std::forward(args)...); 154 | } 155 | 156 | explicit operator bool() const noexcept { return GetImplPtr_() != nullptr; } 157 | const std::type_info &TargetType() const noexcept 158 | { 159 | if (*this) 160 | return GetImplPtr_()->TargetType(); 161 | return typeid(void); 162 | } 163 | 164 | void swap(Function &another) noexcept 165 | { 166 | std::swap(GetImplPtr_(), another.GetImplPtr_()); 167 | } 168 | }; 169 | 170 | // 其他的由编译器自己生成或调换顺序。 171 | template 172 | bool operator==(const Function &f, std::nullptr_t) noexcept 173 | { 174 | return !static_cast(f); 175 | } 176 | 177 | int Test(int a) 178 | { 179 | return a; 180 | } 181 | 182 | int main() 183 | { 184 | Function a{ Test }; 185 | auto result = a(12); 186 | std::println("{} {} {} {}", result, nullptr == a, nullptr != a, 187 | a.TargetType().name()); 188 | Function c = [&](int b) { return a(b) + b; }; 189 | std::println("{}", c(11)); 190 | return 0; 191 | } -------------------------------------------------------------------------------- /12-Advanced Template/Answer-code/Invoke.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace Detail 6 | { 7 | 8 | template 9 | struct InvokeHelper 10 | { 11 | static constexpr decltype(auto) Call(T &&func, auto &&...args) 12 | { 13 | return std::forward(func)(std::forward(args)...); 14 | } 15 | }; 16 | 17 | template 18 | requires std::is_member_pointer_v 19 | struct InvokeHelper 20 | { 21 | template 23 | static constexpr decltype(auto) Call(PointedType ClassType::*func, T &&obj, 24 | Args &&...args) 25 | { 26 | using ObjectType = std::remove_reference_t< 27 | std::unwrap_reference_t>>; 28 | ObjectType &realObj = obj; 29 | static constexpr bool matchCase = 30 | std::is_same_v || 31 | std::is_base_of_v; 32 | 33 | if constexpr (std::is_function_v) 34 | { 35 | if constexpr (matchCase) 36 | { 37 | return (realObj.*func)(std::forward(args)...); 38 | } 39 | else 40 | { 41 | return ((*realObj).*func)(std::forward(args)...); 42 | } 43 | } 44 | else if constexpr (sizeof...(args) == 0) 45 | { 46 | if constexpr (matchCase) 47 | { 48 | return (realObj.*func); 49 | } 50 | else 51 | { 52 | return ((*realObj).*func); 53 | } 54 | } 55 | else 56 | { 57 | static_assert( 58 | false, 59 | "Pointer to data member is not callable with arguments."); 60 | } 61 | } 62 | }; 63 | 64 | } // namespace Detail 65 | 66 | template 67 | decltype(auto) Invoke(F &&func, Args &&...args) 68 | { 69 | return Detail::InvokeHelper>::Call( 70 | std::forward(func), std::forward(args)...); 71 | } 72 | 73 | int Test(int param) 74 | { 75 | std::println("Hello, {}", param); 76 | return param + 1; 77 | } 78 | 79 | class A 80 | { 81 | public: 82 | int m = 0; 83 | int Test(int param) const 84 | { 85 | std::println("Hello, {}", param); 86 | return param + 1; 87 | } 88 | }; 89 | 90 | int main() 91 | { 92 | std::println("----Normal function test----"); 93 | std::println("Hi, {}", Invoke(Test, 1)); 94 | std::println("----Normal function pointer test----"); 95 | std::println("Hi, {}", Invoke(Test, 1)); 96 | 97 | A a; 98 | std::println("----Member function pointer + obj caller test----"); 99 | std::println("Hi, {}", Invoke(&A::Test, A{}, 1)); 100 | std::println("Hi, {}", Invoke(&A::Test, a, 1)); 101 | std::println("Hi, {}", Invoke(&A::Test, std::move(a), 1)); 102 | 103 | std::println( 104 | "----Member function pointer + reference_wrapper obj caller test----"); 105 | std::println("Hi, {}", Invoke(&A::Test, std::ref(a), 1)); 106 | std::println("Hi, {}", Invoke(&A::Test, std::cref(a), 1)); 107 | 108 | std::println("----Member function pointer + pointer caller test----"); 109 | std::println("Hi, {}", Invoke(&A::Test, &a, 1)); 110 | 111 | std::println("----Data member pointer + obj caller test----"); 112 | std::println("Hi, {}", Invoke(&A::m, A{})); 113 | std::println("Hi, {}", Invoke(&A::m, a)); 114 | std::println("Hi, {}", Invoke(&A::m, std::move(a))); 115 | 116 | std::println( 117 | "----Data member pointer + reference_wrapper obj caller test----"); 118 | std::println("Hi, {}", Invoke(&A::m, std::ref(a))); 119 | std::println("Hi, {}", Invoke(&A::m, std::cref(a))); 120 | 121 | std::println("----Data member pointer + pointer caller test----"); 122 | std::println("Hi, {}", Invoke(&A::m, &a)); 123 | 124 | return 0; 125 | } -------------------------------------------------------------------------------- /12-Advanced Template/Answer-code/PushBackGuard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace Detail 7 | { 8 | template 9 | struct IsVector : std::false_type 10 | { 11 | }; 12 | 13 | template 14 | struct IsVector> : std::true_type 15 | { 16 | }; 17 | 18 | } // namespace Detail 19 | 20 | template 21 | void CheckedPushBackGuard(std::tuple args, 22 | std::index_sequence _) 23 | { 24 | std::size_t pushedIdx = 0; 25 | auto checkOne = [&pushedIdx, &args]() { 26 | auto &vec = std::get(args); 27 | auto &&arg = std::get(args); 28 | 29 | using VectorType = std::decay_t; 30 | using ElemType = decltype(arg); 31 | 32 | // Message is possible only after C++26. 33 | static_assert(Detail::IsVector::value); 34 | static_assert( 35 | std::is_constructible_v, ElemType>); 36 | 37 | vec.push_back(std::forward(arg)); 38 | std::println("Pushed {}(th) element.", Idx); 39 | pushedIdx = Idx; 40 | }; 41 | 42 | auto popOne = [pushedIdx, &args]() { 43 | std::get(args).pop_back(); 44 | std::println("Poped {}(th) element.", Idx); 45 | return Idx < pushedIdx; 46 | }; 47 | 48 | try 49 | { 50 | // 实现成lambda,就要用这种很丑的调用方式。。所以也可以把这俩作为模板函数写到Detail去。 51 | // 注意不能checkOne,因为lambda本身不是模板,而是operator()是模板。 52 | (checkOne.template operator()(), ...); 53 | } 54 | catch (...) 55 | { 56 | // Short circuit to avoid unnecessary pop. 57 | (popOne.template operator()() && ...); 58 | throw; 59 | } 60 | } 61 | 62 | template 63 | void PushBackGuard(Ts &&...args) 64 | { 65 | constexpr auto size = sizeof...(args); 66 | static_assert(size % 2 == 0, 67 | "This function should be called with v1, e1, v2, e2..., so " 68 | "element number is times of 2."); 69 | CheckedPushBackGuard(std::tuple{ std::forward(args)... }, 70 | std::make_index_sequence()); 71 | } 72 | 73 | class SomeClassMayThrow 74 | { 75 | int val_; 76 | 77 | public: 78 | SomeClassMayThrow(int val) : val_{ val } {} 79 | SomeClassMayThrow(const SomeClassMayThrow &another) : val_{ another.val_ } 80 | { 81 | throw std::runtime_error{ "Test" }; 82 | std::println("Constructed."); 83 | } 84 | ~SomeClassMayThrow() { std::println("Dtor."); } 85 | auto GetVal() const noexcept { return val_; } 86 | }; 87 | 88 | int main() 89 | { 90 | std::vector a; 91 | a.reserve(4); 92 | a.emplace_back(1); 93 | a.emplace_back(2); 94 | a.emplace_back(3); 95 | 96 | std::vector v{ 1, 2, 3 }; 97 | std::vector v2{ 1, 2, 3 }; 98 | try 99 | { 100 | PushBackGuard(v, 4, a, SomeClassMayThrow{ 4 }, v2, 4.0); 101 | std::println("{}", v.at(3)); 102 | std::println("{}", v2.at(3)); 103 | std::println("{}", a.at(3).GetVal()); 104 | } 105 | catch (const std::exception &ex) 106 | { 107 | std::println("{}", ex.what()); 108 | std::println("{} {} {}", v.size(), v2.size(), a.size()); 109 | } 110 | return 0; 111 | } -------------------------------------------------------------------------------- /12-Advanced Template/Answer.md: -------------------------------------------------------------------------------- 1 | # Answer 2 | 3 | ### Part 1 4 | 5 | 1. 注意`typename T::size_type Size`是NTTP,尽管带着一个`typename`。 6 | 7 | 2. ```c++ 8 | template class A 9 | { 10 | friend void Test(A& a, float); 11 | }; 12 | ``` 13 | 14 | 这里也可以把`int, float`给去掉,可以进行自动推断;但是`<>`不能去掉,否则不会当作模板的实例化,而是会当作另一个普通函数。 15 | 16 | 3. ```c++ 17 | auto Size(const auto& container) { return container.size(); } 18 | template 19 | auto Size(T (&arr)[N]) 20 | { 21 | return N; 22 | } 23 | ``` 24 | 25 | 注意这个参数是不能换为`T arr[N]`的,因为会decay掉,这个`N`不能参与匹配。 26 | 27 | 4. 见`Answer-code/FixedString.cpp`。 28 | 29 | ### Part 2 30 | 31 | 1. 第一个是普通的pack expansion(因此初始化出一个`initializer_list`或者`int`,根据`sizeof...(args)`决定),第二个是fold expression。作用都是顺序输出元素。 32 | 33 | 2. 对于一个节点,返回沿着指定的`path`走到的节点,可以进行如下调用: 34 | 35 | ```c++ 36 | Node node; // 假设进行了一些初始化,构成一个合法的树。 37 | 38 | auto left = &Node::left; 39 | auto right = &Node::right; 40 | Traverse(&node, left, right, left); // 左-右-左的节点。 41 | ``` 42 | 43 | 如果方向全部编译期可确定,可以写成如下形式: 44 | 45 | ```c++ 46 | template // 当然你用auto...也可以 47 | Node* Traverse(Node* np) 48 | { 49 | return (np->* ... ->* paths); 50 | } 51 | 52 | Traverse(&node); 53 | ``` 54 | 55 | 3. ```c++ 56 | template 57 | class A 58 | { 59 | std::tuple tuple_; 60 | 61 | public: 62 | A(Args &&...args) : tuple_{ std::forward(args)... } {} 63 | }; 64 | ``` 65 | 66 | 4. ```c++ 67 | template 68 | auto ForwardAsTuple(Ts&&... args) 69 | { 70 | // 这里的模板参数必须手动加上,不能CTAD,因为CTAD的guide不推导引用。 71 | return std::tuple{ std::forward(args)...}; 72 | } 73 | ``` 74 | 75 | 5. 见`Answer-code/Invoke.cpp`。特别地,可以循规蹈矩地按照规定一条一条来实现,但我们这里对两种情况进行了合并(即消除了`reference_wrapper`的`if constexpr`,也不用自己实现判断实例的traits): 76 | 77 | ```c++ 78 | using ObjectType = std::remove_reference_t< 79 | std::unwrap_reference_t>>; 80 | ObjectType &realObj = obj; 81 | static constexpr bool matchCase = 82 | std::is_same_v || 83 | std::is_base_of_v; 84 | ``` 85 | 86 | 解释一下: 87 | 88 | + 第一层`std::remove_cvref_t`是标准要求的; 89 | + 第二层`std::unwrap_reference_t`对`T = reference_wrapper`返回`U&`,其他返回`T&`。这个是C++20的traits。 90 | + 第三层把这个`&`去掉,得到无reference_wrapper的类型`ObjectType`。 91 | 92 | 随后利用`reference_wrapper`能够自动转型为`T&`的特性,下面这一句就统一了两个case: 93 | 94 | ```c++ 95 | ObjectType &realObj = obj; 96 | ``` 97 | 98 | ### Part 3 99 | 100 | 1. ```c++ 101 | template 102 | struct IsExplicitConvertible : std::false_type 103 | { 104 | }; 105 | 106 | template 107 | struct IsExplicitConvertible< 108 | From, To, std::void_t(std::declval()))>> 109 | : std::true_type 110 | { 111 | }; 112 | 113 | template 114 | constexpr bool IsExplicitConvertibleV = IsExplicitConvertible::value; 115 | ``` 116 | 117 | 2. ```c++ 118 | template 119 | struct IsConvertible 120 | { 121 | private: 122 | static void CheckConvert(To); 123 | static int CheckConvert(...); 124 | 125 | public: 126 | static constexpr bool value = 127 | std::is_same_v())), void>; 128 | }; 129 | 130 | template 131 | constexpr bool IsConvertibleV = IsConvertible::value; 132 | ``` 133 | 134 | 可以测试一下: 135 | 136 | ```c++ 137 | class A 138 | { 139 | public: 140 | operator int() { return 0; } // 加上explicit之后编译不通过。 141 | }; 142 | 143 | static_assert(IsConvertibleV); 144 | static_assert(IsConvertibleV); 145 | ``` 146 | 147 | 特别地,标准里还规定了一些特殊情况,例如`To = void`总是得到`true`,但是这些通过特化做都比较简单,就略过了。 148 | 149 | 3. ```c++ 150 | template 151 | struct IsNothrowConvertible 152 | { 153 | private: 154 | static void CheckConvert(To) noexcept; 155 | static int CheckConvert(...); 156 | 157 | public: 158 | static constexpr bool value = noexcept(CheckConvert(std::declval())); 159 | }; 160 | 161 | template 162 | constexpr bool IsNothrowConvertibleV = IsNothrowConvertible::value; 163 | ``` 164 | 165 | 当`From`不能转为`To`时,匹配到第二个重载,此时由于函数没有`noexcept`而一定得到`value = false`;而如果能转为`To`,则匹配到第一个,函数本身`noexcept`,所以检查的是`From->To`是否为`noexcept`,满足我们的需求。 166 | 167 | 4. 续上一部分第三题中`A` 168 | 169 | ```c++ 170 | private: 171 | template 172 | decltype(auto) applyImpl(F func, std::index_sequence _) 173 | { 174 | return func(std::get(this->tuple_)...); 175 | } 176 | 177 | public: 178 | template F> 179 | decltype(auto) apply(F func) 180 | { 181 | return applyImpl(func, std::make_index_sequence()); 182 | } 183 | ``` 184 | 185 | 几个Note: 186 | 187 | 1. 事实上标准库中还有对这种`sizeof...`的新helper,即可以写为: 188 | 189 | ```c++ 190 | return applyImpl(func, std::index_sequence_for()); 191 | ``` 192 | 193 | 2. `make_integer_sequence`实际上是现有模板不能非常好地实现的(需要$O(N)$的实例化深度,编译不友好),都是编译期开洞完成的(即有一个内置函数来$O(1)$完成这个操作)。但是C++26的静态反射(static reflection)可以解决这个问题,见提案P2996 [Reflection for C++26](https://isocpp.org/files/papers/P2996R9.html#implementing-make_integer_sequence)。 194 | 195 | 3. `apply`这个函数在标准库里实际上就是C++17的`std::apply(tuple, Method)`,例如: 196 | 197 | ```c++ 198 | std::tuple a{ 1, 2.0 }; // 199 | std::apply(a, [](int b, double c) { std::println("{} {}", b, c); }); 200 | ``` 201 | 202 | 5. 见`Answer-code/PushBackGuard.cpp`,还是很有难度的。特别地,从C++26开始才允许使用如下形式的`static_assert`: 203 | 204 | ```c++ 205 | static_assert(condition, msg); // msg.data()返回const char*,msg.size()返回大小。 206 | ``` 207 | 208 | 之前都必须是裸字符串,没法进行复杂操作,所以用户不能知道具体哪个元素出错了。 209 | 210 | ### Part 4 211 | 212 | 1. ```c++ 213 | template 214 | class ObjectCounter 215 | { 216 | protected: 217 | static int counter = 0; 218 | public: 219 | ObjectCounter() noexcept { counter++; } 220 | ObjectCounter(const ObjectCounter&) noexcept { counter++; } 221 | ObjectCounter(ObjectCounter&&) noexcept { counter++; } 222 | ~ObjectCounter() { counter--; } 223 | }; 224 | 225 | // 所有手写的派生类构造函数不需要在initializer里显式写ObjectCounter{},会默认构造。 226 | // 如果派生类的copy/move是自动产生的,则会默认调用基类的copy/move。 227 | class Object : public ObjectCounter 228 | { 229 | }; 230 | ``` 231 | 232 | 2. 见`Answer-code/Function.cpp`,思路难度不大,主要是一些细枝末节可能有小问题。 233 | 234 | 3. 本质上变成了存引用参数的指针而不是存对象本身,当然这就对生命周期有要求。此外由于`function_ref`只存在`operator()`,因此它并没有用虚表,直接把函数指针塞到对象里,这个和我们Lecture 5的作业是一样的。 235 | 236 | 4. 本质上是特化了`move_only_function`等情况;我们知道: 237 | 238 | + 对于非const的特化,能接受任意对象`Obj`(不论其`operator()`是否为`const`),但具有的`operator()`非const,因此`const move_only_function`不可调用。 239 | + 对于const的特化,只能接受`operator() const`的对象,否则编译报错。调用时就是调用这个`operator()`。 240 | 241 | 例如: 242 | 243 | ```c++ 244 | template 245 | class MoveOnlyFunction 246 | { 247 | public: 248 | template 249 | requires std::invocable, Args...> // 只有const可调用才能构造 250 | MoveOnlyFunction(F&&) { } 251 | 252 | decltype(auto) operator(Args... args)() const 253 | { 254 | return GetImplPtr_()->Call(std::forward(args)...); 255 | } 256 | }; 257 | 258 | template 259 | class MoveOnlyFunction 260 | { 261 | public: 262 | template 263 | requires std::invocable, Args...> 264 | MoveOnlyFunction(F&&) { } 265 | 266 | decltype(auto) operator(Args... args)() 267 | { 268 | return GetImplPtr_()->Call(std::forward(args)...); 269 | } 270 | }; 271 | ``` 272 | 273 | 当然实际上除了`const`外,还可以增加noexcept和reference qualifier的限制,写起来这么多特化还是挺麻烦的。在MS-STL里,是多加了一层继承规定这些乱七八糟的东西,之后实际的`MoveOnlyFunction`就可以统一写了。 274 | 275 | > 特别地: 276 | > 277 | > + 目前的`std::move_only_function/copyable_function`还并不存在CTAD。 278 | > + `std::function_ref`不存在reference qualifier的特化,只有const和noexcept的四种组合。 279 | 280 | 5. 见`Answer-code/Function-SBO.cpp`,实现的思路是MS-STL使用的。特别地: 281 | + `Move`虽然标注了noexcept,但是并没有检查移动构造函数是否真的`noexcept`,保险起见可以检查一下`nothrow_copy_constructible || nothrow_move_constructible`,然后`move_if_noexcept`来实现`Move`。 282 | + 你也可以加上一个flag保存是不是trivially copyable,如果是就直接memcpy过去,减少虚函数调用(当然也增加了空间大小)。 283 | -------------------------------------------------------------------------------- /12-Advanced Template/README.md: -------------------------------------------------------------------------------- 1 | # Assignment 2 | 3 | 虽然我们在这一部分留了大量的作业,但是实际的代码中仍然有很多需要更灵活运用的地方。在学完这节课后,你可以自行阅读或者尝试编写标准库中`std::tuple`、`std::variant`等实现(如果不太明白,可以参考C++ Templates The Complete Guide 2nd ed. 24 ~ 26章)。同时,你也需要能够分析出模板的实例化深度(复杂度),来估计实例化对编译时间的影响。 4 | 5 | ### Part 1 6 | 7 | 1. 解释如下模板函数头: 8 | 9 | ```c++ 10 | template void Test(); 11 | ``` 12 | 13 | 1. 对于如下模板函数: 14 | 15 | ```c++ 16 | template class A; 17 | 18 | template void Test(A& a, U); 19 | ``` 20 | 21 | `T = int, U = float`的实例设置为`A`的friend。 22 | 23 | 1. 实现`std::size`,它对于传入的数组也可以返回其大小(其他调用`.size()`)。注意利用引用推断不decay的性质。 24 | 25 | 1. 实现`BasicFixedString`,进行如下改进: 26 | 27 | + 模板增加`typename CharType`,像`std::basic_string`一样可以接受任意字符类型,然后对`char`对应的类型进行alias。 28 | 29 | + 注意到当前是从头到尾复制的,但是实际的字符串都有null termination。将`FixedString`面对`[N+1]`的输入改为存储`[N]`,同时要求仍然可以自动推断出`N`。 30 | 31 | > 同时你可以利用`std::copy`而不是自己写一个循环,前者已经是`constexpr`的了。 32 | 33 | + 写一个`operator+`,产生新的`BasicFixedString`。 34 | 35 | + 补全方法`c_str`、`data`、`size`、`begin`、`end`。 36 | 37 | + 实现一个`view`方法,返回对应的string_view。 38 | 39 | 记得给各个函数加上`constexpr`。同时,msvc可能会出现内部编译器错误(Internal Compiler Error, ICE),可以换用gcc和clang。 40 | 41 | > msvc是所有编译器里最容易出现ICE的,这就是闭源的坏处。。 42 | 43 | ### Part 2 44 | 45 | 1. 分别解释如下两个语句: 46 | 47 | ```c++ 48 | template 49 | void Print(const Args &...args) 50 | { 51 | auto result = { (std::cout << args << " ", 0)... }; 52 | ((std::cout << args << " "), ...); 53 | } 54 | ``` 55 | 56 | 1. 解释如下代码,并说明应该如何调用: 57 | 58 | ```c++ 59 | struct Node 60 | { 61 | int value; 62 | Node* left = nullptr; 63 | Node* right = nullptr; 64 | }; 65 | 66 | template 67 | Node* Traverse(Node* np, TP... paths) { 68 | return (np ->* ... ->* paths); 69 | } 70 | ``` 71 | 72 | 如果所有的`path`都是编译期可确定的,可以使用NTTP改成什么样子? 73 | 74 | 1. 我们之前所讲的都是对一个pack的解包,但有些时候我们是无法得到一个pack的(例如,我们需要把它们存为成员),此时可以利用`std::tuple`。写一个模板类,它可以存储任意类型的tuple,构造函数对这个tuple进行构造。 75 | 76 | 1. 实现`std::forward_as_tuple`,对于传入的参数,构造相应引用形式的`tuple`并返回。 77 | 78 | > 这个函数的一个使用场合是,当有可能有多组`Args`构造不同的元素时,仅凭一个变长模板参数是无法区分哪些属于哪个元素的构造参数,这时候要传递tuple,但还要进行正常的forward。例如对于`std::map::emplace(args...)`,`args`需要构造`std::pair`,如果Key和Value都需要多个元素构造,则可以利用其构造函数: 79 | > 80 | > ```c++ 81 | > template< class... Args1, class... Args2 > 82 | > pair(std::piecewise_construct_t, 83 | > std::tuple first_args, 84 | > std::tuple second_args); 85 | > ``` 86 | > 87 | > 于是可以: 88 | > 89 | > ```c++ 90 | > map.emplace(std::piecewise_construct, 91 | > std::forward_as_tuple(1, 2.0f), 92 | > std::forward_as_tuple(1, "123")); // 假设Key和Value分别进行这种构造。 93 | > ``` 94 | > 95 | > `try_emplace(Key, Args...)`本质上在插入成功时就等价于 96 | > 97 | > ```c++ 98 | > map.emplace(std::piecewise_construct, 99 | > std::forward_as_tuple(Key), 100 | > std::forward_as_tuple(Args...)); 101 | > ``` 102 | 103 | 1. 选做题:实现`std::invoke`,注意对不同类型的可调用对象的重载或特化(可以利用`std::is_member_pointer`等member pointer的traits,见[std::is_member_pointer](https://en.cppreference.com/w/cpp/types/is_member_pointer))。具体要求参见[std::invoke](https://en.cppreference.com/w/cpp/utility/functional/invoke)及[Function objects](https://en.cppreference.com/w/cpp/utility/functional)。 104 | 105 | > 注:可以使用如下方式得到member pointer的两个组成: 106 | > 107 | > ```c++ 108 | > template 109 | > decltype(auto) Call(PointedType ClassType::* param); 110 | > 111 | > Call(&A::Test); // ClassType == A; 112 | > // 当Test为函数时,PointedType为相应的函数类型;否则就是member object pointer. 113 | > ``` 114 | 115 | ### Part 3 116 | 117 | 前三题请全部使用SFINAE而非concept完成。 118 | 119 | 1. 实现`is_explicit_convertible`,即可以通过`static_cast`来进行`From->To`的转型。 120 | 121 | > 这个是标准库没有的traits,仅作为练习。 122 | 123 | 1. 实现`is_convertible`,即可以通过**隐式转型**来进行`From->To`的转型。 124 | 125 | > Hint: 通过函数参数转型。 126 | 127 | 1. 实现`is_nothrow_convertible`,增加了转型过程为`noexcept`的要求。 128 | 129 | > Hint: `noexcept(Exp)`表示当Exp各个步骤都是noexcept时才为`true`。特别地,对于function call,就代表了函数本身`noexcept`,同时各个参数的传递也是noexcept的。 130 | 131 | 1. 对于上一部分的第3题,可以发现pack存为`tuple`后就不能再进行unpack了,但有些情况下我们可能需要这种unpack。例如: 132 | 133 | ```c++ 134 | int Func(int a, double b) { return 1; } 135 | 136 | A params{ 1, 2.0 }; // 假设我们存为tuple的类为A 137 | auto result = params.apply(Func); // 等价于Func(1, 2.0);(当然准确说是存成左值再传进去) 138 | ``` 139 | 140 | 我们又知道,`std::tuple`是可以用`std::get`来得到第Idx个成员的。所以一种直观的做法是,我们可以通过NTTP来实现`apply`: 141 | 142 | ```c++ 143 | template 144 | decltype(auto) apply(T func) 145 | { 146 | return func(std::get(this->tuple)...); 147 | } 148 | ``` 149 | 150 | 然而,这需要用户进行如下形式的调用: 151 | 152 | ```c++ 153 | params.apply<0, 1>(Func); 154 | ``` 155 | 156 | 但是我们事实上需要的就是`0 ~ sizeof...(args)`的序列,直观上来说模板应该有自动生成的方式。标准库中提供了类`integer_sequence`和函数`make_integer_sequence`,后者可以生成前者(`integer_sequence`)。当`T = std::size_t`时,又增加了别名`index_sequence`和`make_index_sequence`。 157 | 158 | 我们不妨看一个例子,以非循环的方式输出`std::array`: 159 | 160 | ```c++ 161 | template 162 | void OutputAllImpl(T &arr, std::index_sequence _) 163 | { 164 | ((std::cout << arr[Indices] << '\n'), ...); 165 | } 166 | 167 | template 168 | void OutputAll(const std::array &arr) 169 | { 170 | OutputAllImpl(arr, std::make_index_sequence()); 171 | } 172 | 173 | int main() 174 | { 175 | std::array arr{ 1, 2, 3 }; 176 | OutputAll(arr); 177 | } 178 | ``` 179 | 180 | 请利用类似的方法实现`apply`。 181 | 182 | 1. 选做题:实现`PushBackGuard(v1, e1, v2, e2, ...)`,对`vi`推入元素`ei`,同时要求强异常安全性,即一旦有一个推入失败,该函数已推入的元素需要pop出去,再把异常原封不动地抛出去。 183 | 184 | > 最后补充一下User-defined literals在模板上的知识: 185 | > 186 | > + 对于整数和浮点数,如果没有其他可能,则会尝试使用如下方式调用: 187 | > 188 | > ```c++ 189 | > operator""X<'c1 ', 'c2 ', 'c3 ', ..., 'ck '>() 190 | > ``` 191 | > 192 | > 因此,你可以编写如下的user-defined literals: 193 | > 194 | > ```c++ 195 | > template 196 | > constexpr double operator ""_x() 197 | > { 198 | > // 利用Chars转成你想要的返回类型。 199 | > } 200 | > ``` 201 | > 202 | > + 对于字符串,从C++20开始,如果没有其他可能,则会尝试使用如下方式调用: 203 | > 204 | > ```c++ 205 | > operator""X() 206 | > ``` 207 | > 208 | > 因此可以利用我们前面的`BasicFixedString`来编写user-defined literals: 209 | > 210 | > ```c++ 211 | > template 212 | > constexpr double operator ""_x() 213 | > { 214 | > // 利用Chars转成你想要的返回类型。 215 | > } 216 | > ``` 217 | 218 | ### Part 4 219 | 220 | 1. 有些情况下,我们可能需要统计对象的生存情况,可以像这样来完成: 221 | 222 | ```c++ 223 | class Object 224 | { 225 | static inline int counter = 0; 226 | public: 227 | Object() { counter++; } 228 | Object(const Object&) { counter++; } 229 | Object(Object&&) { counter++; } 230 | ~Object() { counter--; } 231 | }; 232 | ``` 233 | 234 | 不过如果要统计的类太多,就要写很多重复代码。思考如何利用CRTP来消除这种冗余。 235 | 236 | 1. 阅读`std::function`的文档,实现`std::function`;忽略allocator相关的部分(事实上C++17也把`std::function`的allocator支持移除了,见[P0302: Removing Allocator Support in std::function](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0302r1.html)),也不考虑SBO。 237 | 238 | 1. 从实现的角度,思考一下C++26的`std::function_ref`和`std::function`在成员和构造函数上的区别。 239 | 240 | 1. 我们之前说过,`std::move_only_function`可以解决`std::function`的const问题,即`const std::function`不能只调用底层对象的`const`方法,而却仍然可以调用非const方法。为了解决这个问题,可以引入一个非常古老的“特性”(Abominable Function Type,“令人憎恶的函数类型”),即: 241 | 242 | ```c++ 243 | using F1 = void(); 244 | using F2 = void() const &&; 245 | ``` 246 | 247 | `F1`就是一个普通的函数类型,而`F2`加了一堆的后缀,但我们知道这些后缀本质上是修饰成员函数的。因此,`F2`并不能正常地使用(例如不能定义它的指针或者引用类型),但是`F1`和`F2`的差异却给模板特化带来了可能。据此,猜测`std::move_only_function`如何解决const问题。 248 | 249 | 1. 选做题:实现`std::function`的SBO。 250 | 251 | > 注意,buffer如何在两个function之间传递是比较麻烦的,尤其是考虑到具有虚函数的类不是trivially copyable的,不能进行逐字节的拷贝(也就不能通过buffer的swap来完成)。现有的实现有两种方式: 252 | > 253 | > + MS-STL:对proxy实现`Move`这个虚函数,对buffer的转移实际上是通过移动构造新的object。优点是实现起来相对简单,同时不要求SBO的优化只发生在trivially copyable的object上。 254 | > + libstdc++:手动实现虚表,存储函数指针,和我们Lecture 5的作业最后一题比较相似。同时要求接受的functor是trivially copyable的。优点是调用的虚函数更少,在trivially copyable的情况下效率更高。 255 | > 256 | > 而libc++则两种都实现了,根据ABI选项来确定实际使用哪种策略。当然有一些实际区别,例如: 257 | > 258 | > + 对于MS-STL策略,没有移动而是拷贝来进行swap;等等。 259 | > + 对于libstdc++策略,放松了trivially copyable的要求,要求trivially destructible等(因为libstdc++事实上也没有完全利用trivially copyable);等等。 260 | > 261 | > 但是总体思路大差不差。你可以选择一种进行实现。 262 | 263 | > 如果你想,也可以实现一下它的deduction guide。 264 | -------------------------------------------------------------------------------- /12-Advanced Template/code/CRTP and Type Erasure/CRTP-deducing this.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class StudentBase 5 | { 6 | public: 7 | using Super = StudentBase; 8 | float GetGPA(this auto &&self) { return self.GetGPACoeff() * 4.0f; } 9 | }; 10 | 11 | class Student : public StudentBase 12 | { 13 | friend Super; 14 | float GetGPACoeff() { return 0.8f; } 15 | }; 16 | 17 | class JuanWang : public StudentBase 18 | { 19 | friend Super; 20 | float GetGPACoeff() { return 1.0f; } 21 | }; 22 | 23 | template 24 | requires std::derived_from 25 | void PrintGPA(T &student) 26 | { 27 | std::println("{}", student.GetGPA()); 28 | } 29 | 30 | int main() 31 | { 32 | Student s; 33 | PrintGPA(s); 34 | JuanWang s2; 35 | PrintGPA(s2); 36 | } -------------------------------------------------------------------------------- /12-Advanced Template/code/CRTP and Type Erasure/CRTP.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | class StudentBase 5 | { 6 | public: 7 | using Super = StudentBase; 8 | float GetGPA() 9 | { 10 | return static_cast(this)->GetGPACoeff() * 4.0f; 11 | } 12 | }; 13 | 14 | class Student : public StudentBase 15 | { 16 | friend Super; 17 | float GetGPACoeff() { return 0.8f; } 18 | }; 19 | 20 | class JuanWang : public StudentBase 21 | { 22 | friend Super; 23 | float GetGPACoeff() { return 1.0f; } 24 | }; 25 | 26 | template 27 | void PrintGPA(StudentBase &student) 28 | { 29 | std::println("{}", student.GetGPA()); 30 | } 31 | 32 | int main() 33 | { 34 | Student s; 35 | PrintGPA(s); 36 | JuanWang s2; 37 | PrintGPA(s2); 38 | } -------------------------------------------------------------------------------- /12-Advanced Template/code/CRTP and Type Erasure/Type Erasure.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | class StudentProxyBase 6 | { 7 | public: 8 | virtual float GetGPACoeff() = 0; 9 | virtual StudentProxyBase *clone() const = 0; 10 | virtual ~StudentProxyBase() = default; 11 | }; 12 | 13 | template 14 | class StudentProxy : public StudentProxyBase 15 | { 16 | ConcreteStudentType student; 17 | 18 | public: 19 | // You can also add another overload for ConcreteStudentType&&. 20 | StudentProxy(const ConcreteStudentType &init_student) 21 | : student{ init_student } 22 | { 23 | } 24 | float GetGPACoeff() override { return student.GetGPACoeff(); } 25 | StudentProxy *clone() const override { return new StudentProxy{ student }; } 26 | }; 27 | 28 | class StudentBase 29 | { 30 | StudentProxyBase *studentProxy = nullptr; 31 | 32 | public: 33 | StudentBase() = default; 34 | 35 | template 36 | StudentBase(T &&student) 37 | { 38 | studentProxy = new StudentProxy{ std::forward(student) }; 39 | } 40 | 41 | StudentBase(const StudentBase &another) 42 | : studentProxy{ another.studentProxy->clone() } 43 | { 44 | } 45 | 46 | StudentBase(StudentBase &&another) 47 | : studentProxy{ std::exchange(another.studentProxy, nullptr) } 48 | { 49 | } 50 | 51 | float GetGPA() 52 | { 53 | if (studentProxy == nullptr) 54 | { 55 | throw std::runtime_error{ 56 | "Cannot deference without underlying student" 57 | }; 58 | } 59 | return studentProxy->GetGPACoeff() * 4.0f; 60 | } 61 | ~StudentBase() { delete studentProxy; } 62 | }; 63 | 64 | class Student 65 | { 66 | public: 67 | float GetGPACoeff() { return 0.8f; } 68 | }; 69 | 70 | class JuanWang 71 | { 72 | public: 73 | float GetGPACoeff() { return 1.0f; } 74 | }; 75 | 76 | void PrintGPA(StudentBase student) 77 | { 78 | std::println("{}", student.GetGPA()); 79 | } 80 | 81 | int main() 82 | { 83 | PrintGPA(Student{}); 84 | PrintGPA(JuanWang{}); 85 | } -------------------------------------------------------------------------------- /12-Advanced Template/code/Lazy.cpp: -------------------------------------------------------------------------------- 1 | template // This should be std::size_t at best; we just want to 2 | // illustrate lazy instantiation, so here we need N <= 0 3 | // to make Array ill-formed. 4 | class Array 5 | { 6 | public: 7 | int arr[N]; 8 | }; 9 | 10 | template 11 | T v = T::default_value(); 12 | 13 | template 14 | class A 15 | { 16 | public: 17 | using M = T::value_type; 18 | 19 | void Func(T a = T{}) {} 20 | 21 | void error() { Array<-1> boom; } 22 | 23 | // Successful ones 24 | void Test() { Array arr; } 25 | struct Test2 26 | { 27 | Array arr; 28 | }; 29 | 30 | void Test4(Array *arr) {} 31 | 32 | // Failed ones 33 | void Test3(Array arr) {} 34 | union { 35 | Array arr; 36 | int m; 37 | }; 38 | }; 39 | 40 | int main() 41 | { 42 | A a; 43 | } -------------------------------------------------------------------------------- /12-Advanced Template/code/NTTP.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct RGB 5 | { 6 | int r, g, b; 7 | }; 8 | 9 | template 10 | void Test(RGB &color) 11 | { 12 | color.*Channel = 0; 13 | } 14 | 15 | void Func(int p) 16 | { 17 | std::cout << p; 18 | } 19 | 20 | template 21 | void Test2(int param) 22 | { 23 | Func(param); 24 | } 25 | 26 | template 27 | class A 28 | { 29 | public: 30 | constexpr decltype(auto) operator()() { return Callable(); } 31 | }; 32 | 33 | int main() 34 | { 35 | RGB color{ 1, 2, 3 }; 36 | Test<&RGB::g>(color); 37 | std::cout << color.g; 38 | 39 | Test2<&Func>(1); 40 | Test2(1); // Also correct; 41 | 42 | A<[]() { return 0; }> a; 43 | A<[]() { std::cout << "Hello, world"; }> b; 44 | } 45 | -------------------------------------------------------------------------------- /12-Advanced Template/code/SFINAE/DefaultConstructible.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | struct IsDefaultConstructible 5 | { 6 | private: 7 | template> 8 | static char test(void *); 9 | template 10 | static long test(...); 11 | 12 | public: 13 | static constexpr bool value = 14 | std::is_same_v(nullptr)), char>; 15 | }; 16 | 17 | template 18 | struct IsDefaultConstructible2 : std::false_type 19 | { 20 | }; 21 | 22 | template 23 | struct IsDefaultConstructible2> : std::true_type 24 | { 25 | }; 26 | 27 | class A 28 | { 29 | public: 30 | A(int); 31 | }; 32 | 33 | static_assert(IsDefaultConstructible::value); 34 | static_assert(IsDefaultConstructible2::value); 35 | static_assert(!IsDefaultConstructible::value); 36 | static_assert(!IsDefaultConstructible2::value); 37 | 38 | int main() {} -------------------------------------------------------------------------------- /12-Advanced Template/code/SFINAE/Dictionary.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace Detail 6 | { 7 | template 8 | struct SupportLessThan : std::false_type 9 | { 10 | }; 11 | 12 | template 13 | struct SupportLessThan< 14 | Key, std::void_t() < std::declval())>> 15 | : std::true_type 16 | { 17 | }; 18 | 19 | template 20 | struct SupportHash : std::false_type 21 | { 22 | }; 23 | 24 | template 25 | struct SupportHash{}(std::declval()))>> 27 | : std::true_type 28 | { 29 | }; 30 | 31 | template 32 | using ProperDictionary = 33 | std::conditional_t::value, std::map, 34 | std::unordered_map>; 35 | } // namespace Detail 36 | 37 | template 38 | class Dictionary 39 | { 40 | }; 41 | 42 | template 43 | class Dictionary::value || 45 | Detail::SupportHash::value>> 46 | : public Detail::ProperDictionary 47 | { 48 | }; 49 | 50 | // template 51 | // class Dictionary::value>> 53 | // : public std::map 54 | // { 55 | // }; 56 | 57 | // template 58 | // class Dictionary::value && 60 | // !Detail::SupportLessThan::value>> 61 | // : public std::unordered_map 62 | // { 63 | // }; 64 | 65 | class Integer 66 | { 67 | int num_; 68 | 69 | public: 70 | Integer(int n) : num_{ n } {} 71 | auto GetNum() const noexcept { return num_; } 72 | // 不定义operator<=>,只允许进行相等的比较。 73 | bool operator==(const Integer &) const = default; 74 | // C++23也可以用bool operator==(this Integer, Integer) = default; 75 | // 来进行值传递,总之要保持两个参数的类型一样。 76 | }; 77 | 78 | template<> 79 | struct std::hash 80 | { 81 | auto operator()(Integer integer) const noexcept 82 | { 83 | return std::hash{}(integer.GetNum()); 84 | } 85 | }; 86 | 87 | int main() 88 | { 89 | Dictionary map; 90 | map[1] = 2; 91 | 92 | Dictionary umap; 93 | umap[1] = 2; 94 | } -------------------------------------------------------------------------------- /12-Advanced Template/code/SFINAE/advance.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace MyStd 8 | { 9 | 10 | template 11 | constexpr bool IsRandomAccess = std::is_convertible_v< 12 | typename std::iterator_traits::iterator_category, 13 | std::random_access_iterator_tag>; 14 | 15 | template 16 | constexpr bool IsInput = std::is_convertible_v< 17 | typename std::iterator_traits::iterator_category, 18 | std::input_iterator_tag>; 19 | 20 | template 21 | std::enable_if_t> advanceIterImpl(Iterator &x, 22 | Distance n) 23 | { 24 | std::println("Call random-access"); 25 | x += n; 26 | } 27 | 28 | template 29 | std::enable_if_t<(IsInput && !IsRandomAccess)> 30 | advanceIterImpl(Iterator &x, Distance n) 31 | { 32 | std::println("Call input"); 33 | while (n > 0) 34 | ++x, --n; 35 | } 36 | 37 | template 38 | void advance(Iterator &x, Distance n) 39 | { 40 | advanceIterImpl(x, n); 41 | } 42 | 43 | } // namespace MyStd 44 | 45 | int main() 46 | { 47 | std::vector v{ 1, 2, 3 }; 48 | auto it = v.begin(); 49 | // 不加MyStd会导致ADL,非常烦人。。 50 | MyStd::advance(it, 3); 51 | std::println("Equiv: {}", it == v.end()); 52 | 53 | std::list l{ 1, 2, 3 }; 54 | auto it2 = l.begin(); 55 | MyStd::advance(it2, 3); 56 | std::println("Equiv: {}", it2 == l.end()); 57 | } -------------------------------------------------------------------------------- /12-Advanced Template/code/SFINAE/conditional.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // WRONG: 4 | // template 5 | // struct TryUnsignedT 6 | // { 7 | // using type = 8 | // std::conditional_t && !std::is_same_v, 9 | // std::make_unsigned_t, T>; 10 | // }; 11 | 12 | template 13 | struct TryUnsignedT 14 | { 15 | using type = 16 | std::conditional_t && !std::is_same_v, 17 | std::make_unsigned, std::type_identity>::type; 18 | }; 19 | 20 | using T = TryUnsignedT::type; 21 | 22 | int main() {} -------------------------------------------------------------------------------- /12-Advanced Template/code/Variadic Template/Ex1-3.cpp: -------------------------------------------------------------------------------- 1 | #include "print.h" 2 | #include 3 | #include 4 | #include 5 | 6 | // Ex 1 - 3 7 | template 8 | decltype(auto) Invoke(F func, Args &&...args) 9 | { 10 | return func(std::forward(args)...); 11 | } 12 | 13 | template 14 | void print_elems(const T &container, IdxTypes... idx) 15 | { 16 | print(container[idx]...); 17 | } 18 | 19 | template 20 | class A 21 | { 22 | public: 23 | template 24 | static auto func() 25 | { 26 | return std::tuple...>(); 27 | } 28 | }; 29 | 30 | int main() 31 | { 32 | using ExpectedType = 33 | std::tuple, std::pair>; 34 | std::same_as auto result = A::func(); 35 | } -------------------------------------------------------------------------------- /12-Advanced Template/code/Variadic Template/Ex4-6.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Ex 4 - 6 4 | template 5 | int h(Args... args) 6 | { 7 | return sizeof...(Args); 8 | } 9 | 10 | template 11 | void g(Args... args) 12 | { 13 | print(h(args) + args...); 14 | print(h(args...) + args...); 15 | } 16 | 17 | template 18 | class A : public T1... 19 | { 20 | public: 21 | // 注意不能用T1&&,因为不是universal reference. 22 | template 23 | A(T2 &&...args) : T1{ std::forward(args) }... 24 | { 25 | } 26 | }; 27 | 28 | template 29 | A(T2...) -> A; 30 | 31 | class B 32 | { 33 | }; 34 | class C 35 | { 36 | }; 37 | 38 | int main() 39 | { 40 | A a{ B{}, C{} }; 41 | } -------------------------------------------------------------------------------- /12-Advanced Template/code/Variadic Template/Ex7-9.cpp: -------------------------------------------------------------------------------- 1 | #include "print.h" 2 | #include 3 | #include 4 | #include 5 | 6 | // Ex 7 - 9 7 | template 8 | struct Overloaded : Funcs... 9 | { 10 | public: 11 | using Funcs::operator()...; 12 | }; 13 | 14 | // Only needed in C++17. 15 | template 16 | Overloaded(Args...) -> Overloaded; 17 | 18 | template 19 | int h(Args... args) 20 | { 21 | return sizeof...(Args); 22 | } 23 | 24 | template 25 | void g(Args... args) 26 | { 27 | print(h(args) + args...); 28 | print(h(args...) + args...); 29 | } 30 | 31 | template 32 | void Access(const T &container) 33 | { 34 | container[Indices]...; 35 | } 36 | 37 | template 38 | void Error(Ts... args, T arg); 39 | 40 | int main() 41 | { 42 | auto overloads = Overloaded{ [](int i) { return std::to_string(i); }, 43 | [](const std::string &s) { return s; }, 44 | [](auto &&val) { return std::string{}; } }; 45 | std::variant v{ 1 }; 46 | std::visit(overloads, v); 47 | 48 | g(1, 2); 49 | 50 | std::vector v2{ 1, 2, 3 }; 51 | Access<1, 2>(v2); 52 | } -------------------------------------------------------------------------------- /12-Advanced Template/code/Variadic Template/print.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | void print(T firstArg, const Args &...args) 5 | { 6 | std::cout << firstArg << "\n"; 7 | if constexpr (sizeof...(args) != 0) 8 | { 9 | print(args...); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /12-Advanced Template/现代C++基础 - 模板进阶.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Extra-Creativity/Modern-Cpp-Basics/b9d584cfbbc10d2e72875507dea1b7ca20071645/12-Advanced Template/现代C++基础 - 模板进阶.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modern-Cpp-Basics 2 | 实验环境:Visual Studio 2022(更新至最新),其他平台也是最新编译器与版本;等gcc14在四月份发布的时候会做一个docker给大家用。 --------------------------------------------------------------------------------