vault backup: 2025-03-04 11:17:00

This commit is contained in:
2025-03-04 11:17:00 +08:00
parent d1e51bfd2f
commit ff12c4f4ca
161 changed files with 1 additions and 2 deletions

View File

@@ -0,0 +1 @@
[AnthonyCalandra/modern-cpp-features: A cheatsheet of modern C++ language and library features.](https://github.com/AnthonyCalandra/modern-cpp-features)

39
20.02. CPP/C++17.md Normal file
View File

@@ -0,0 +1,39 @@
- 變數宣告的方式變了
- Old: `int a = 3;`
- New: `int a {3};`
- `if`裡面可以宣告變數
```cpp
if (auto a {3}; a > b) {
// Do something
}
```
- `unique_ptr`: 無法複製的指標
- 傳統方法:
```cpp
unique_ptr<uint8_t[]> buffer = new uint8_t[256];
```
- 新方法:
```cpp
auto buffer = std::make_unique<uint8_t[]>(256);
```
- `share_ptr`: 可以複製,但要避免循環參考問題
- 透過refernce傳遞array參數
- 考慮一個帶有長度的陣列要傳到function裡面但是又希望在function面可以指定陣列長度
```cpp
double value[] { 1.0, 2.0, 3.0 }; // Error!
double value[] { 1.0, 2.0, 3.0, 4.0, 5.0 }; // Pass!
double average(const double (&array)[5]) {
...
}
```
- 用 `std::string_view` 代替 `const std::string&`。
## Multi-Thread
### 使用`std::async`
- [C++ 使用 Async 非同步函數開發平行化計算程式教學](https://blog.gtwang.org/programming/cpp-11-async-function-parallel-computing-tutorial/)
- [std::atomic](https://en.cppreference.com/w/cpp/atomic/atomic)

View File

@@ -0,0 +1 @@
[C++20, How Hard Could It Be](https://docs.google.com/presentation/d/1HwLNSyHxy203eptO9cbTmr7CH23sBGtTrfOmJf9n0ug/edit?fbclid=IwAR2wToW9uFJuLtUR9nftfv9N9axXwPK7HmuJWqgVmCeXd1XJF7ySQIkNsJM&resourcekey=0-GH5F3wdP7D4dmxvLdBaMvw#slide=id.g1c5cc391dd_2_295)

97
20.02. CPP/C++20.md Normal file
View File

@@ -0,0 +1,97 @@
## Modules
Modules provide a new way to organize and manage large codebases, helping to improve build times, reduce code duplication, and increase modularity.
```cpp
// Example: math.cppm
export module math;
export int square(int x) {
return x * x;
}
export int cube(int x) {
return x * x * x;
}
// Example: main.cpp
import math;
int main() {
int result = math::square(5);
// result = 25
int result2 = math::cube(5);
// result2 = 125
}
```
## Concepts
Concepts provide a way to specify **_constraints_** on templates, making it easier to write **_generic_** code that works with a wide range of types.
```cpp
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> T;
};
template <Addable T>
T add(T a, T b) {
return a + b;
}
int main() {
int x = 1, y = 2;
auto result = add(x, y);
// result = 3
}
```
## Ranges
Ranges provide a new way to manipulate **_sequences_** of data in C++, making it easier to write clean, readable, and efficient code.
```cpp
#include <ranges>
#include <iostream>
int main() {
std::vector<int> v{1, 2, 3, 4, 5};
auto even = v | std::ranges::views::filter([](int x) { return x % 2 == 0; });
for (int x : even) {
std::cout << x << '\n';
}
}
```
## Contract Programming
Contract programming allows developers to specify **_preconditions_**, **_postconditions_**, and **_assertions_** in their code, making it easier to **catch** bugs early and reduce the number of runtime errors.
```cpp
#include <iostream>
[[nodiscard]] int foo() {
return 42;
}
int main() {
int x = foo();
std::cout << x << '\n';
}
```
## Improved Template Metaprogramming
C++20 includes several imrovements to the way templates can be used in C++, making it easier to write **generic** code that can be used with a wide range of types.
```cpp
#include <type_traits>
template <typename T>
struct is_integral : std::false_type {};
template <>
struct is_integral<int> : std::true_type {};
int main() {
static_assert(is_integral<int>::value, "");
static_assert(!is_integral<float>::value, "");
}
```

View File

@@ -0,0 +1,23 @@
> Class template類別樣板不是類別而是建立類別的方法。
定義類別樣板
```cpp
template <template parameter list>
class ClassName
{
// Template class definition
};
```
`typename`來指定會變動的變數型態,例:
```cpp
template <typename T1, typename T2>
class MyTemplateClass
{
public:
T1 length;
T2 weight;
};
```

274
20.02. CPP/GCC.md Normal file
View File

@@ -0,0 +1,274 @@
GCC的全稱是GNU Compiler Collection是GNU工具鏈中的一種。GCC不僅支持C/C++語言還支持Fortran/Ada/Java等語言的編譯。GCC和gcc是兩個概念GCC是工具鏈的集合裡面除了gcc/g++還包含了cclcclplus等組件。gcc/g++只是GCC工具鏈的一個子集。
## g++和gcc的區別
gcc可以判斷出目標程序所使用編程語言的類別會把xxx.c文件當作C語言編譯把xxx.cpp文件當作C++語言編譯。而g++只把xxx.c和xxx.cpp一律都當作C++語言來編譯。在編譯C++文件的時候g++會自動鏈接一些標準庫或基礎庫而gcc不會。當正在編譯的C++代碼文件依賴STL標準庫的時候為了使用STLgcc命令需要增加參數`lstdc++`。因此雖然gcc和g++都可以編譯C++語言程序但是使用g++會更方便一些。
## 常見文件副檔名
- 目標文件:
- `xxx.o`Linux, Mac
- `xxx.obj`windows
- 二進製文件:
- `xxx`沒有副檔名Linux, Mac, FreeBSD,
- `xxx.exe`windows
- `xxx.hex`:嵌入式系統
- 共享庫文件,也叫動態庫文件:
- `xxx.dll`windows
- `xxx.so`Linux
- `xxx.dylib`Mac
- 靜態庫文件
- `xxx.a`
## C/C++語言的編譯過程
1. 預處理
預處理命令聲明了編譯時需要的各種頭文件和宏,比如包含哪些頭文件、宏定義的擴展、在哪個代碼段做條件編譯等。涉及預處理的語法有:#define,#include,#ifdef...#endif
2. 編譯
首先檢查代碼的規範性和語法錯誤等,檢查完畢後把代碼翻譯成彙編語言,生成彙編語言文件
3. 彙編
基於彙編語言文件生成二進制格式的目標文件
4. 鏈接
將目標代碼與所依賴的庫文件進行關聯或者組裝,合成一個可執行文件
具體過程如圖:
![[Pasted image 20220926211701.png]]
### 拿g++舉例
1. 樣例代碼:
```cpp
#include <iostream>
int main() {
    std::cout << "Hello World!" << std::endl;
    return 0;
}
```
2. g++的編譯第一步是預處理:將`xx.cpp`源文件預處理成`xx.i`文件
```cpp
g++ -E demo.cpp -o demo.i
```
3. 第二步是編譯:將`xx.i`文件編譯為`xx.s`的組合語言文件。此時只進行編譯生成組合語言程式碼,而不對代碼以彙編的方式調試
```js
g++ -S demo.i -o demo.s
```
3. 第三步是彙編:將`xx.s`文件彙編成`xx.o`的二進制目標文件
```cpp
g++ -c demo.s -o demo.o
```
4. 第四步是鏈接:將`xx.o`二進製文件進行鏈接,最終生成可執行程序
```cpp
g++ demo.o -o demo.out
```
![[Pasted image 20220926212043.png]]
## 靜態鏈接和動態鏈接的區別
### 靜態庫
與目標程序合併,成為目標程序的一部分。
創建靜態庫的時候,需要使用`gcc/g++ -c`先將xxx.c源文件編譯為目標文件xxx.o然後使用`ar`指令將xxx.o打包成xxxx.a靜態庫。
目標程序與靜態庫鏈接時,目標程序代碼調用的任何外部函數的代碼都會從靜態庫中復製到最終的可執行文件中。
GCC在鏈接時優先使用動態庫只有當動態庫不存在時才開始使用靜態庫如果要強制使用靜態庫編譯時加上`-static`參數。
使用`-Wl``-Bstatic`告訴鏈接器優先使用靜態庫。
### 動態庫
不包含在目標程序中,但是與目標程序相關聯。
創建動態庫的時候,可以傳`-shared``-fPIC`參數,`-fPIC`參數用於編譯階段,用來生成位置無關的代碼。使用`gcc -shared -fPIC`可以直接用xxx.c源文件生成xxx.so動態庫。
目標程序與動態庫鏈接時,可執行文件僅包含它所需的一個小函數表,而不是來自庫文件的完整機器代碼。在可執行文件開始運行之前,動態庫的代碼被操作系統複製到內存中進行共享。
動態庫之所以叫共享庫,可能是由於動態庫的代碼副本可以在多個程序之間共享。正因為這種鏈接方式,共享庫每次被更新時,都不需要重新編譯正在使用共享庫的目標程序。
使用`-Wl``-Bdynamic`告訴鏈接器優先使用動態庫。
### 有關的環境變量
`LIBRARY_PATH`:使用於編譯期間,目標程序鏈接時搜索動態庫的路徑。
`LD_LIBRARY_PATH`:使用於目標程序生成後,目標程序運行時搜索動態庫的路徑
### 靜態庫鏈接時,搜索庫文件路徑的順序
1. `ld`會去找GCC命令中的參數`-L`
2. gcc的環境變量`LIBRARY_PATH`
3. `/lib``/usr/lib``/usr/local/lib`等寫在程序內的路徑
### 動態庫鏈接時,搜索庫文件路徑的順序
1. 編譯目標代碼時指定的動態庫搜索路徑
2. gcc的環境變量`LD_LIBRARY_PATH`
3. 配置文件`/etc/ld.so.conf`中指定的動態庫搜索路徑
4. 默認的動態庫搜索路徑`/lib`
5. 默認的動態庫搜索路徑`/usr/lib`
## 實用的工具
### ldd
列出依賴的動態庫
![[Pasted image 20220926212505.png]]
### nm
查看動態庫/靜態庫中的函數
![[Pasted image 20220926212522.png]]
## gcc/g++命令常見參數
命令格式
```bash
gcc [-c|-S|-E] [-std=standard]
           [-g] [-pg] [-Olevel]
           [-Wwarn...] [-pedantic]
           [-Idir...] [-Ldir...]
           [-Dmacro[=defn]...] [-Umacro]
           [-foption...] [-mmachine-option...]
           [-o outfile] [@file] infile...
```
在linux環境使用`man g++`可以查看g++命令常用參數
### 常見參數如下(注意大小寫):
```bash
-o
#输出到指定文件。如果不指定默认输出到a.out
-E
#仅进行预处理,不进行编译、汇编和链接
-S
#将代码转换为文件格式为xxx.s的汇编语言文件但不进行汇编
-c
#仅进行编译和汇编不进行链接操作常用于编译不包含main程序的子程序代码
-v
#打印gcc编译时的详细步骤信息
复制代码
```
### 編譯和路徑參數
```bash
-l[basic library]
#编译时指定要使用的基础库,样例:-lpthread针对Posix线程共享库进行编译
-L[shared-library path]
#共享库的路径添加到搜索的范围路径为包含xxx.dll/xxx.so/xxx.dlyb文件的目录
-I[include header-file path]
#将头文件的路径添加到搜索的范围路径为包含xxx.h/xxx.hpp文件的目录
-shared
#生成共享库库文件格式为xxx.dll/xxx.so/xxx.dlyb格式的文件
-static
#生成静态库库文件格式为xxx.a格式的文件
-Wl
#告诉编译器将后面的参数传递给链接器
-Wl,-Bstatic
#-Bstatic选项用于对指定的库静态连接
-Wl,-Bdynamic
#-Bdynamic搜索共享库默认
-Wa,option
#此选项传递option给汇编程序;如果option中间有逗号,就将option分成多个选项,然后传递给会汇编程序
-Wl,option
#此选项传递option给连接程序;如果option中间有逗号,就将option分成多个选项,然后传递给会连接程序
复制代码
```
### 預處理參數
```bash
#使用形式:-D[FLAG] 或-D[FLAG]=VALUE
-Dmacro
#在命令行里定义宏相当于C语言中的"#define macro"
-Umacro
#相当于C语言中的"#undef macro"
-undef
#取消对任何非标准宏的定义
复制代码
```
### 警告與報錯參數
```bash
-Wall
#发出gcc提供的所有有用的报警信息
-Werror
#将警告升级为编译报错
-Wextra / -W
#启用-Wall未启用的额外警告位对合法但值得怀疑的代码发出警告 例如 -Wsign-compare
-pendantic / -Wpendantic
#发出ISO C和ISO C++标准列出的所有警告,用于语法检查,-pedantic-erros的用法也类似
-fsyntax-only
#仅做语法检查
复制代码
```
### 調試參數
```bash
-g
#产生带有调试信息的目标代码
-gstabs
#此选项以stabs格式声称调试信息,但是不包括gdb调试信息
-gstabs+
#此选项以stabs格式声称调试信息,并且包含仅供gdb使用的额外调试信息
-ggdb
#生成gdb专用的调试信息
-glevel
#请求生成调试信息同时用level指出需要多少信息默认的level值是2
复制代码
```
### 編碼配置參數
```bash
-fno-exceptions
#屏蔽掉C++的异常,常用于于嵌入式或无法接受异常的系统
-fno-rtti
#禁用RTTI常用于嵌入式或游戏开发
-fno-asm
#不要识别asm,inline或typeof作为关键字以便代码可以使用这些词作为标识符。您可以使用关键字__asm__,__inline__来__typeof__ 代替。 -ansi暗示-fno-asm
-fPIC / -fpic
#让编译器的代码和位置无关,让代码逻辑不使用绝对地址,只用相对地址,方便文件加载
-nostdinc
#使编译器不再系统默认的头文件目录里面找头文件, 一般和 -I 联合使用,明确限定头文件的位置
-nostdin C++
#规定不在g++指定的标准路经中搜索,但仍在其他路径中搜索,.此选项在创建libg++库使用
复制代码
```
### 優化參數
```bash
-O0
#不优化
-O1 / -O
#尝试优化编译时间和可执行文件大小
-O2
#尝试所有的优化选项,但不会进行“空间换时间”的优化方式
-Os
#尝试所有的优化选项时,优先优化可执行文件大小
复制代码
```
## 來源
- [C/C++生態工具鏈——gcc/g++編譯器使用指南- 掘金](https://juejin.cn/post/7143280156042330125)
## 參考
- [Top (Using the GNU Compiler Collection (GCC))](https://gcc.gnu.org/onlinedocs/gcc/)
- [The C++ Compilation Model | C++ Fundamentals](https://subscription.packtpub.com/book/programming/9781789801491/1/ch01lvl1sec03/the-c-compilation-model)
- [What is LD_LIBRARY_PATH used for?](https://linuxhint.com/what-is-ld-library-path/)

View File

@@ -0,0 +1,16 @@
1. Use std::shared_ptr & std::unique_ptr & std::weak_ptr
2. Use std::array or std::vector
3. Use structured binding & std::tuple
4. Use for (auto& elem : collector)
5. Use std::format
6. Use std::optional
7. Use auto for return type
8. Use auto in variable declaration
9. Use `using` to replace `#define`
10. Use "Lambda expression" and std::function
11. `[[deprecated]]` attribute
12. Maybe...std::any?
13. And more, constexpr, concept,
## Reference
- [AnthonyCalandra/modern-cpp-features: A cheatsheet of modern C++ language and library features.](https://github.com/AnthonyCalandra/modern-cpp-features)

View File

@@ -0,0 +1 @@
[Modern C++ use in Chromium](https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/c++/c++-features.md#Declaring-non_type-template-parameters-with-auto-tbd)

View File

@@ -0,0 +1,68 @@
---
tags: cpp17
aliases:
date: 2025-02-10
time: 17:34:18
description:
---
Structured binding declaration 可以把對應的 tuple、pair、vector 展開,讓 code 更好讀。
## 展開 tuple
假設我們有一個 tuple
```cpp
std::tuple<std::string, uint32_t, uint32_t> person{ "John", 32, 170 };
auto& [name, age, tall] = person;
```
name 會是 "John"
age 是 32
tall 是 170
但比較好用的時候還是用來展開 function 的回傳值,假設我們有一個 function 會回傳 tuple
```cpp
std::tuple<std::string, uint32_t, uint32_t> getPersonData(uint32_t id) {
return std::make_tuple("John", 32, 170);
}
auto [name, age, tall] = getPersonData(id);
```
## 展開 array
`std::vector` 也是一樣的用法:
```cpp
#include <array>
std::array<int, 3> my_vec = { 5, 7, 10 };
auto& [num1, num2, num3] = my_vec;
```
或是:
```cpp
float rect[4]{ 5.0f, 6.0f, 120.0f, 200.0f };
auto& [x, y, w, h] = rect;
```
但是不能用來展開 `std::vector`
## 展開 pair
```cpp
std::pair<std::string, int32_t> name_phone{ "John", 912345678 };
auto& [name, phone_number] = name_phone;
```
用在 for-loop 裡也比較好懂,假設我們有一個 vector 用來存剛剛的姓名跟電話:
```cpp
std::vector<std::pair<std::string, uint32_t>> phoneBook = {
{ "John", 912345678 },
{ "Andy", 912345679 },
};
for (const auto& [name, phone] : phoneBook) {
std::cout << "Name: " << name << ", phone: " << phone << std::endl;
}
```
# 參考來源

85
20.02. CPP/chrono.md Normal file
View File

@@ -0,0 +1,85 @@
---
tags: cpp11
aliases:
date: 2025-02-10
time: 17:38:00
description:
---
## header
```cpp
#include <chrono>
```
## duration
duration 是 chrono 裡面,用來記錄時間長度的類別,他基本上是一個 template class可以自行定義他的意義chrono 也有提供一些比較常見的時間類別可以直接拿來使用下面就是內建的 duration 的型別
```cpp
typedef duration<long long, nano> nanoseconds;
typedef duration<long long, micro> microseconds;
typedef duration<long long, milli> milliseconds;
typedef duration<long long> seconds;
typedef duration<int, ratio<60> > minutes;
typedef duration<int, ratio<3600> > hours;
```
其中可以看到,第一個 template 參數是要用來儲存資料的型別第二個則是他相對於「秒」的比例。這邊也使用了 ratio 這個 C++11 的另一個新的函式庫的類別,他是用來記錄「有理數」(可以寫成分數的數)的新類別,有興趣可以參考 [cppreference 的介紹](http://en.cppreference.com/w/cpp/numeric/ratio/ratio)。
基本上,一般會用到時間單位這邊都有定義好了,如果不合用的話,也可以自己去定義;而由於 chrono 也有把相關的計算都定義了,所以也可以直接拿來做計算,就算是時間單位不同,也不會有問題。
下面就是一個簡單的例子:
```cpp
std::chrono::minutes t1( 10 );
std::chrono::seconds t2( 60 );
std::chrono::seconds t3 = t1 - t2;
std::cout << t3.count() << " second" << std::endl;
```
其中t1 是代表 10 分鐘、 t2 是代表 60 秒t3 則是 t1 減去 t2也就是 600 60 = 540 秒。
而如果要取得一個 duration 的值的話則是要呼叫他的 count() 這個函式像在上面的例子裡面就會把 t3 的值輸出所以最後會出現「540 second」。
而如果想要做強制的時間單位轉換也可以使用 duration_cast<>() 這個函式來做下面就是一個把以秒為單位的 t3 轉換成分鐘後再輸出。
```cpp
cout << chrono::duration_cast<chrono::minutes>( t3 ).count() << endl;
```
## time_point
相較於 duration 是用來紀錄時間的長度的time_point 是用來記錄一個特定時間點的資料類別。他一樣是一個 template class需要指定要使用的 clock 與時間單位duration
Chrono 一般來說有提供兩種 clock 可以使用分別是system_clock  steady_clock。其中 system_clock 是直接去抓系統的時間有可能在使用中會被被修改[參考](http://en.cppreference.com/w/cpp/chrono/system_clock) steady_clock 則是確實地去紀錄時間的流逝所以不會出現時間倒退的狀況[參考](http://en.cppreference.com/w/cpp/chrono/steady_clock))。
一般要使用的話,大概會是下面的樣子:
```cpp
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
std::cout << "Hello World\n";
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
std::cout << "Printing took "
<< std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count()
<< "us.\n";
```
透過 clock 類別所提供的 now() 這個函式可以快速地取得現在的時間而兩者相減的話則會產生一個型別為 duration 的結果在上面的例子裡面就是一開始先取得當下的時間 t1然後輸出一個字串後、再去取得一個時間 t2之後兩者相減就可以取得中間過程所花費的時間了。在這邊則是在相減後把結果轉換成以 micro second 為單位後,再做輸出。
 time_point 也可以和 duration 做計算得出新的 time_point例如下面的程式碼就是計算 10 個小時候的時間:
```cpp
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::chrono::system_clock::time_point nt = now + std::chrono::hours(10);
```
另外chrono 也有定義 high_resolution_clock提供更高的精確度[參考](http://en.cppreference.com/w/cpp/chrono/high_resolution_clock));但是實際上在 MSVC11 上他就等同於 system_clock。
而如果要把不同定義的 time_point 做轉換則也可以使用 time_point_cast<>() 這個函式來處理,不過這邊就不多加說明了。
## time_point 的輸出
STL 的 chrono 並沒有定義 time_point 的輸出方式所以我們並不能直接透過 output stream 來輸出 time_point 的資料所以如果要把他輸出成字串的話其實還有點麻煩…
如果想要輸出的話,一個方法是透過 clock 提供的 to_time_t() 這個函式 time_point 先把他轉換成 C-style 的 time_t然後再透過 ctime() 這類的函式做輸出;下面是一個簡單的範例:
```cpp
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t( now );
std::cout << std::ctime( &now_c ) << std::endl;
```
而如果是使用 Boost 的版本的話Boost 則是另外有提供 chrono_io.hpp 這個檔案在裡面替 duration  time_point 定義了輸出的格式可以直接使用相當地方便有興趣的話可以參考 [Boost 的官方說明](http://www.boost.org/doc/libs/1_55_0/doc/html/chrono/users_guide.html#chrono.users_guide.tutorial.i_o)。
# 參考來源
- [C++11 STL 的時間函式庫chrono Heresy's Space](https://kheresy.wordpress.com/2013/12/27/c-stl-chrono/)

35
20.02. CPP/for_each.md Normal file
View File

@@ -0,0 +1,35 @@
---
tags: cpp17
aliases:
date: 2025-02-10
time: 17:37:31
description:
---
for_each 是一個 function它的原型是
```cpp
template<class InputIterator, class Function>
Function for_each(
InputIterator _Start,
InputIterator _Last,
Function _Func
);
```
它需要3個參數第1個是開始的iterator第2是結束的 iterator第3個是要用來處理的 function。
一個簡單的例子有一個array需要把每一個數都加1
```cpp
vector<int> arr1 = { 4, 5, 8, 3, 1 };
for_each(
arr1.begin(), // _Start
arr1.end(), // _Last
[](int& val) { // _Func
val += 1;
}
);
```
# 參考來源

223
20.02. CPP/lambda.md Normal file
View File

@@ -0,0 +1,223 @@
---
tags: cpp11, cpp14
aliases:
date: 2022-06-12
time: 18:21:42
description:
---
一個簡單的 Lamdba 運算式:
```cpp
[] (int x, int y) -> bool {
return x < y;
}
```
- 以中括號開頭,中括號被稱為*lamdba 導入器lamdba introducer*
- 小括號裡面是*lamdba 參數列表lambda parameter list*
- 如果沒有參數,小括號可以省略,`[] () {...}` 可以簡寫成 `[] {...}`
- 箭號(`->`)後面是回傳的型別,如果沒寫就由 `return` 自動推斷
將 Lamdba 運算式指定給變數:
```cpp
auto comapre = [] (int x, int y) -> bool {
return x < y;
};
```
## Lamdba的擷取子句
以中括號開頭的 *lamdba 導入器* 可以將外部的變數傳給 Lamdba 運算式正式名稱是「擷取子句capture clause」。
`[=]` 表示它們會以值擷取captured by value。Scope內的變數可以在 lamdba 內使用,但是不可以改變。
`[&]` 表示它們會以參考擷取captured by reference。Scope內的變數可以在 lamdba 內使用,可以改變。
## 以值擷取captured by value
假設有一段程式如下:
```cpp
void testLambda() {
float notUsed = 1.0f;
std::vector<int32_t> numlist{10, 20, 30, 50, 60};
auto findInRange = [=](int32_t start, int32_t end) {
for (auto num : numlist) {
if (num >= start && num <= end) return true;
}
return false;
};
std::cout << "Result: " << findInRange(25, 35) << "\n";
}
```
`[=]`可以用來擷取 lamdba scope 範圍所及的變數,沒有在 Lamdba 運算式裡面被用到的變數就不會被擷取,例如 `float notUsed = 1.0f;`
另一個重點是:**被擷取的變數是不可以更改的**。例如,不能在 lambda 裡面這樣寫:
```cpp
auto findInRange = [=](int32_t start, int32_t end) {
numlist.push_back(5); // ERROR!
for (auto num : numlist) {
if (num >= start && num <= end) return true;
}
return false;
};
```
如果一定要在 lambda 內改變擷取的變數,那必須指名 lambda 為 `mutable`
```cpp
auto findInRange = [=](int32_t start, int32_t end) mutable { // <-- assign mutable
numlist.push_back(5);
for (auto num : numlist) {
if (num >= start && num <= end) return true;
}
return false;
};
```
根據書上解釋 ,可以裡解為 compiler 會將 lamdba 編為一個 class像是
```cpp
class __Lambda8C1A5 {
public:
__Lambda8C1A5(const std::vector<int32_t>& arg1) : numlist(arg1) {}
auto operator()(int32_t start, int32_t end) const { // const!
for (auto num : numlist) {
if (num >= start && num <= end) return true;
}
return false;
}
private:
std::vector<int32_t> numlist;
};
```
這也解釋了 lamdba 的擷取範圍與原理。而 `mutable` 則是讓 `operator()` 不為 `const`,如下:
```cpp
auto findInRange = [=](int32_t start, int32_t end) mutable { // <-- assign mutable
numlist.push_back(5);
for (auto num : numlist) {
if (num >= start && num <= end) return true;
}
return false;
};
...
class __Lambda8C1A5 {
public:
__Lambda8C1A5(const std::vector<int32_t>& arg1) : numlist(arg1) {}
auto operator()(int32_t start, int32_t end) { // No const here
for (auto num : numlist) {
if (num >= start && num <= end) return true;
}
return false;
}
private:
std::vector<int32_t> numlist;
};
```
## 以值擷取特定的變數
若只需要擷取特定的變數,那就直接在 lamdba 導入器(就是`[]`)寫入變數名稱,例如:
```cpp
int var1 = 10;
int var2 = 20;
int var3 = 30;
auto afunc = [var1, var2] () {
...
};
```
## 以參考擷取captured by reference
`[&]` 會擷取 scope 內的所有外部變數,而且可以修改:
```cpp
void testLambda() {
float notUsed = 1.0f;
std::vector<int32_t> numlist{ 10, 20, 30, 50, 60 };
auto findInRange = [&](int32_t start, int32_t end) { // Use & here
numlist.push_back(100); // OK
for (auto num : numlist) {
if (num >= start && num <= end) return true;
}
return false;
};
std::cout << "Result: " << findInRange(25, 35) << "\n";
std::cout << "numlist: ";
for (auto n : numlist) {
std::cout << n << " ";
}
std::cout << "\n"; // Output numlist: 10 20 30 50 60 100
}
```
## 以參考擷取特定的變數
但是直接參考全部的外部變數不是好的作法,這讓你有機會做出一些意外的修改,所以請擷取有需要的變數就好:
```cpp
void testLambda() {
float notUsed = 1.0f;
std::vector<int32_t> numlist{ 10, 20, 30, 50, 60 };
auto findInRange = [&numlist](int32_t start, int32_t end) {
numlist.push_back(100); // OK
for (auto num : numlist) {
if (num >= start && num <= end) return true;
}
return false;
};
...
}
```
如果有多個變數需要擷取,那就用 `,` 分開:
```cpp
auto findInRange = [&numlist, &var1, &var2](int32_t start, int32_t end) {
...
};
```
## 混合擷取
以值擷取跟參考擷取也可以寫在一起:
```cpp
auto findInRange = [=, &numlist](int32_t start, int32_t end) {
...
};
```
上面的例子中,`numlist` 會是參考擷取,其他的外部變數則是以值擷取。
或是:
```cpp
auto findInRange = [&, numlist](int32_t start, int32_t end) {
...
};
```
上面的例子中,`numlist` 會以值擷取,其他的外部變數則是參考擷取。
但是,如果已經使用了 `=` ,就不可以再以值擷取其他變數,像是 `[=, numlist]` 就是不合法的。
反之,如果已經使用了 `&`,就不可以再參考擷取其他變數,像是 `[&, &var1]` 就是不合法的。
## 存取 class
Lamdba 寫在 class 裡面的時候,不論 [[lambda#以值擷取captured by value|以值擷取]]或是 [[lambda#以參考擷取captured by reference|以參考擷取]]都沒辦法傳遞成員變數member variable只能傳遞 `this`,透過 `this` 來存取成員變數。例:
```cpp
class BigBuffer {
public:
void modify(int x, int y, ...) {
auto modifyBuffer = [this] () { // Use this
if (buffer) { // equal to this->buffer
// do something with buffer
}
};
...
}
private:
uint32_t bufferSize = 0;
std::unique_ptr<uint8_t[]> buffer = nullptr;
};
```

3
20.02. CPP/lvalue.md Normal file
View File

@@ -0,0 +1,3 @@
lvalue 是指:
- 等號左邊的值
- 可以被「取址」的變數

View File

@@ -0,0 +1,70 @@
---
tags: cpp14
aliases:
date: 2025-02-10
time: 17:35:40
description:
---
move operator可以讓[[rvalue]]被參考從而進一部的消除複製的成本。例如以下的function會回傳一個很大的陣列
```cpp
vector<int> generateBigArray() {
const int size = 1000000;
vector<int> array;
for (int i = 0; i < size; i++) {
array[i] = i;
}
return array;
}
```
當我們呼叫這個function並把結果回傳到一個變數的時候每一次這個大陣列都會被複製一次
```cpp
vector<int> atemp = generateBigArray(); // 複製1000000個int
```
如果使用[[rvalue]] reference就可以避開這些複製
```cpp
vector<int>&& atemp = generateBigArray(); // 已經建立好的array會直接「移動」到atemp省下了複製的步驟
```
## move contructor
move contructor跟copy constructor很類似只是參數由`&`改成了`&&`
例:
```cpp
template <typename T>
inline Array<T>::Array(const Array&& moved) :
size{moved.size},
elements{moved.elements}
{
moved.elements = nullptr;
}
```
## move assignment operator
```cpp
template <typename T>
Array<T>& Array<T>::operator=(const Array&& rhs)
{
if (this != &rhs) {
delete [] elements;
elements = rhs.elements;
size = rhs.size;
rhs.elements = nullptr;
}
return *this;
}
```
## 明確的「移動」
如果有一個現存的「大東西」,可以使用`std::move`來把它「移」到別的地方,進而避開了複製的行為。例:
```cpp
std::vector<std::string> my_dictionary(10000000);
std::vector<std::string> dictionary2 = std::move(my_dictionary);
```
`std::move`之後my_dictionary的size會變成0。
# 參考來源

210
20.02. CPP/rvalue.md Normal file
View File

@@ -0,0 +1,210 @@
rvalue 是指:
- 等號右邊的值
- 臨時的值,例如運算的結果
- 無法被取址address-of的物件
## rvalue reference
一般的參考只能參考[[lvalue]]如下的程式是ok的
```cpp
int a = 10;
int& b = a;
```
但是像這樣就不行了:
```cpp
int a = 10;
int b = 5;
int& c = a + b;
```
因為`a+b`是一個 rvalue臨時的值沒辦法取址所以無法參考。
但是可以用`&&`來參考 rvalue。例如
```cpp
int a = 10;
int b = 5;
int&& c = a + b; // c = 15
```
而不用這樣:
```cpp
int a = 10;
int b = 5;
int r = a + b;
int& c = r;
```
了解 rvalue reference 之後,就可以實作類別的 move constructor 跟 move assignment operator。這可以減少複製的成本。
## Move constructor
假設我們有一個 class 叫 BigBuffer定義如下
```cpp
class BigBuffer {
public:
BigBuffer(int size=100*1024*1024) :
bufferSize(size)
{
std::cout << "BigBuffer constructor\n";
this->buffer = std::make_unique<uint8_t[]>(bufferSize);
}
~BigBuffer() {
std::cout << "BigBuffer destructor\n";
}
BigBuffer(const BigBuffer& src) {
std::cout << "BigBuffer copy constructor\n";
bufferSize = src.bufferSize;
buffer = std::make_unique<uint8_t[]>(bufferSize);
std::memcpy(buffer.get(), src.buffer.get(), bufferSize);
}
BigBuffer& operator= (BigBuffer& src) {
std::cout << "BigBuffer copy operator\n";
bufferSize = src.bufferSize;
buffer = std::make_unique<uint8_t[]>(bufferSize);
std::memcpy(buffer.get(), src.buffer.get(), bufferSize);
return *this;
}
private:
int bufferSize = 0;
std::unique_ptr<uint8_t[]> buffer = nullptr;
};
```
這個 class 的特色就是每一次使用都會佔用100MB的記憶體空間想像下面的程式的動作
```cpp
BigBuffer buf1;
// Do something with buf1
// Assign to buf2
BigBuffer buf2 = buf1;
```
執行訊息:
```
BigBuffer constructor // create buf1
BigBuffer copy constructor, copy 104857600Bytes // copy buf1 to buf2
...
```
這會先產生 buf1然後把 buf1 copy 給 buf2。如果我們想要省下 copy 的成本,這時候 Move constructor 就可以派上用場了。
幫 BigBuffer 加一個 Move constructor
```cpp
class BigBuffer {
public:
...
BigBuffer(BigBuffer&& src) noexcept {
std::cout << "BigBuffer move constructor\n";
bufferSize = src.bufferSize;
buffer = std::move(src.buffer);
src.buffer.reset();
src.bufferSize = 0;
}
...
};
```
這個 move constructor 的參數就是一個 rvalue reference我們把來源的 bufferSize 跟 buffer 指標「移到」我們這邊,而不是完整的複製一份。在轉移之後呢,當然也要把來源清空,讓轉移更加明確。
有了 Move assignment operator 之後,在執行一次原本的程式,你會發現訊息......沒有變,還是一樣呼叫 copy constructor 來複製了100MB 的 buffer這時我們需要明確的告訴 compiler 我們要「移動」物件,而不是複製它,把原本的程式改為:
```cpp
BigBuffer buf1;
// Do something with buf1
// Assign to buf2
BigBuffer buf2 = std::move(buf1);
```
我們用 `std::move()` 來「移動」物件,這時輸出變成
```
BigBuffer constructor // create buf1
BigBuffer move constructor // move buf1 to buf2, buf1 has nullptr now
...
```
另外一個情形也可以受益於此,假如我們有個 function 會產生 `BigBuffer`,如下:
```cpp
BigBuffer BigBufferCreator() {
std::cout << "BigBufferCreator: Create a BigBuffer!\n";
BigBuffer tempb;
// do something
std::cout << "BigBufferCreator: return\n";
return tempb;
}
BigBuffer b = BigBufferCreator(); // copy tempb to b
```
在沒有 Move constructor 的情況下,上面的程式會先產生一個 `tempb`,然後複製給 `b`,訊息:
```
BigBufferCreator: Create a BigBuffer!
BigBuffer constructor
BigBufferCreator: return
BigBuffer copy constructor, copy 104857600Bytes // Copy 100MB!
...
```
在有 Move constructor 的情況下,訊息就變成:
```
BigBufferCreator: Create a BigBuffer!
BigBuffer constructor
BigBufferCreator: return
BigBuffer move constructor // Use MOVE!
BigBuffer destructor
BigBuffer destructor
```
因為 `BigBufferCreator()` 產生的就是一個 `BigBuffer` rvalue所以 compiler 會使用 move constructor`BigBuffer(BigBuffer&& src)` 而不是 copy constructor。
## Move assignment operator(`=`)
Move assignment operator 的行為跟 move constructor 是一樣的,幫 `BigBuffer` 加入 move assignment operator
```cpp
class BigBuffer {
public:
...
BigBuffer& operator=(BigBuffer&& src) noexcept {
std::cout << "BigBuffer move operator\n";
bufferSize = src.bufferSize;
buffer = std::move(src.buffer);
src.buffer.reset();
src.bufferSize = 0;
return *this;
}
...
};
```
測試程式:
```cpp
BigBuffer b1, b2;
b2 = b1;
```
訊息:
```
BigBuffer constructor
BigBuffer constructor
BigBuffer copy operator, copy 104857600Bytes
```
還是使用 copy assignment operator 來複製,理由是一樣的,需要一個明確的 `std::move()` 來表示「轉移」的行動,把程式改成:
```cpp
BigBuffer b1, b2;
b2 = std::move(b1);
```
這樣就可以了。訊息:
```
BigBuffer constructor
BigBuffer constructor
BigBuffer move operator // Use MOVE!
```
## 參考
- [Value categories - cppreference.com](https://en.cppreference.com/w/cpp/language/value_category)
- [rvalue 參考](https://openhome.cc/Gossip/CppGossip/RvalueReference.html)
- [Move constructors - cppreference.com](https://en.cppreference.com/w/cpp/language/move_constructor)
- [Move assignment operator - cppreference.com](https://en.cppreference.com/w/cpp/language/move_assignment)

File diff suppressed because it is too large Load Diff

110
20.02. CPP/智慧指標.md Normal file
View File

@@ -0,0 +1,110 @@
[`unique_ptr`](https://en.cppreference.com/w/cpp/memory/unique_ptr)與[`shared_ptr`](https://en.cppreference.com/w/cpp/memory/shared_ptr)都是智慧指標箱對於原本的raw pointer智慧指標使用起來更方便也不用擔心delete的問題。
## unique_ptr
`unique_ptr` 的特點是,它保證在一個時間內,只會有一個指標的擁有者,也就是這個指標不能被複製跟移動,當 `unique_ptr` 離開它的scope時候它所擁有的指標也隨之被delete。這讓你不用擔心memory leak的問題。
假設我們有一個class叫 `BigBuffer` ,原本分配記憶體的方法:
```cpp
BigBuffer* bigBuf = new BigBuffer(bufferSize);
// Use buffer here
delete bigBuf;
```
`unique_ptr`
```cpp
auto bigBuf = std::make_unique<BigBuffer>(bufferSize);
// Use buffer here
// bigBuf will be released when exiting scope
```
我們統一用[`std::make_unique<>`](https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique)這個template function來建立 `unique_ptr` ,角括號 `<>` 裡面要帶入你要建立的型別,後面的括號 `()` 就是型別的constructor使用起來跟 `new` 是一樣的。
因為 `std::make_unique<>` 裡面已經有表明型別了,所以變數就用 `auto` 就可以了,不用再寫一次型別。
一旦 `unique_ptr` 建立之後,使用起來就跟一般指標沒有兩樣,都是用 `->` 來操作:`bigBuf->setXXX()` or `bigBuf->getXXX()`
但是別忘記 `unique_ptr` 本身還是一個local variable所以我們可以用 `.` 來操作 `unique_ptr` ,例如我們可以用 `.reset()` 重新配一個指標:
```cpp
BigBuffer* pBuffer = new BigBuffer();
bigBuf.reset(pBuffer);
```
這時候舊指標會自動delete如果記憶體分配有成功的話bigBuf會接管剛剛new出來的指標或者變成 `nullptr` (記憶體分配失敗)。
如果單純想要釋放指標,那就單純的呼叫 `reset()` 就好。
```cpp
bigBuf.reset(); // Now I'm nullptr
```
如果要分配陣列的話:
```cpp
auto intArray = std::make_unique<int[]>(1024);
```
使用方式也是一樣的:
```cpp
intArray[5] = 555;
```
不過對於陣列的操作更建議使用 `std::array`
如果有什麼特殊原因讓你決定不再讓 `unique_ptr` 來幫你管理指標,可以用 `release()` 來讓出指標:
```cpp
auto intArray = std::make_unique<int[]>(1024);
int* intArrayRaw = intArray.release(); // Now I don't care anymore
```
但是這時候呼叫 `delete[]` (或 `delete` )的責任又回到你身上了。所以千萬不要把 `release()``reset()` 搞混了。
`unique_ptr` 不能被複製跟移動,所以下列的寫法都編不過:
```cpp
auto ptr1 = std::make_unique<int>(5);
std::unique_ptr<int> ptr2(ptr1); // Error
std::unique_ptr<int> ptr2 = ptr1; // Error
```
在Visual Studio 2017上錯誤訊息是這樣`error C2280: 'std::unique_ptr<int,std::default_delete<int>>::unique_ptr(const std::unique_ptr<int,std::default_delete<int>> &)': attempting to reference a deleted function`
其實就是`unique_ptr`的copy constructor跟assignment operator都被標記為delete了。
### Move a unique_ptr
如果一定要把 unique_ptr 指定給別人可以嗎?可以的,用 `std::move()` 來轉移:
```cpp
auto ptr1 = std::make_unique<int>(5);
// do something
auto anotherPtr = std::move(ptr1);
```
ptr1原本所管理的指標會轉移給 anotherPtrptr1 會變成 nullptr。
## shared_ptr
建立一個 `shared_ptr` 是使用[`std::make_shared()`](https://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared)
```cpp
auto myBuf = std::make_shared<BigBuffer>(bufferSize);
```
但是 `shared_ptr` 可以被複製與移動,這是跟 `unique_ptr` 的差別:
```cpp
auto myBuf = std::make_shared<BigBuffer>(bufferSize);
std::shared_ptr<BigBuffer> bufCopy = myBuf;
```
現在 bufCopy 跟 myBuf 都指向同一個指標,他們都可以操作這個指標:
```cpp
myBuf->setZero(startAddr, endAddr);
bufCopy->setOne(startAddr, endAddr);
```
`shared_ptr` 內部有一個參考記數reference count來紀錄它所擁有的指標已經分享給幾個變數了只要有變數離開了他的scope參考記數就會減少反之要是像上面那樣有人複製指標參考記數就會增加參考記數歸0的時候指標就會被釋放。
有了 `shared_ptr` 我們就不必擔心 delete 的責任問題:
```cpp
std::shared_ptr<BigBuffer> getBuffer(int32_t bufferSize) {
return std::make_shared<BigBuffer>(bufferSize);
}
int main() {
auto myBuf = getBuffer(1024); // new(malloc) memory
// use myBuf
return 0;
} // myBuf delete memory here
```
`shared_ptr` 有一個問題是可以會「循環參考」cyclic references也就是 share_ptr A 指向另一個 share_ptr B ,然後 share_ptr B 又指向 share_ptr A這造成參考記數reference count不會減少而永遠無法釋出指標。這個是需要注意的。
但是 `shared_ptr` 還是讓記憶體的管理問題大大減少,應該用 `shared_ptr` 來代替 `new` & `delete`