vault backup: 2025-07-22 22:14:08
Affected files: Too many files to list
This commit is contained in:
@@ -1 +0,0 @@
|
||||
[AnthonyCalandra/modern-cpp-features: A cheatsheet of modern C++ language and library features.](https://github.com/AnthonyCalandra/modern-cpp-features)
|
||||
@@ -1,39 +0,0 @@
|
||||
- 變數宣告的方式變了
|
||||
- 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)
|
||||
@@ -1 +0,0 @@
|
||||
[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)
|
||||
@@ -1,97 +0,0 @@
|
||||
## 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, "");
|
||||
}
|
||||
```
|
||||
@@ -1,23 +0,0 @@
|
||||
> 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;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
GCC的全稱是GNU Compiler Collection,是GNU工具鏈中的一種。GCC不僅支持C/C++語言,還支持Fortran/Ada/Java等語言的編譯。GCC和gcc是兩個概念,GCC是工具鏈的集合,裡面除了gcc/g++還包含了ccl,cclplus等組件。gcc/g++只是GCC工具鏈的一個子集。
|
||||
|
||||
## g++和gcc的區別
|
||||
gcc可以判斷出目標程序所使用編程語言的類別,會把xxx.c文件當作C語言編譯,把xxx.cpp文件當作C++語言編譯。而g++只把xxx.c和xxx.cpp一律都當作C++語言來編譯。在編譯C++文件的時候,g++會自動鏈接一些標準庫或基礎庫,而gcc不會。當正在編譯的C++代碼文件依賴STL標準庫的時候,為了使用STL,gcc命令需要增加參數`–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/)
|
||||
@@ -1,16 +0,0 @@
|
||||
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)
|
||||
@@ -1 +0,0 @@
|
||||
[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)
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# 參考來源
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
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/)
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
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;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
# 參考來源
|
||||
@@ -1,223 +0,0 @@
|
||||
---
|
||||
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;
|
||||
};
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
lvalue 是指:
|
||||
- 等號左邊的值
|
||||
- 可以被「取址」的變數
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
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。
|
||||
|
||||
|
||||
# 參考來源
|
||||
@@ -1,210 +0,0 @@
|
||||
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
@@ -1,110 +0,0 @@
|
||||
[`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原本所管理的指標會轉移給 anotherPtr,ptr1 會變成 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` 。
|
||||
Reference in New Issue
Block a user