新增關鍵字(keywords)新增識別符號(Identifies)模組(Modules)優點建立模組引用模組import 標頭檔案Ranges例子協程(Coroutines)什麼是協程例子(VC++)Concepts如何定義使用例子Lambda 表示式的更新[=, this] 需要顯式捕獲this變數模板形式的 Lambda 表示式Lambda 表示式打包捕獲(Pack Expansion)常量表達式(constexpr) 的更新constexpr string & vector原子(Atomic)智慧指標例子自動合流(Joining), 可中斷(Cancellable) 的執行緒例子C++20 同步(Synchronization)庫std::atomic_ref其他更新指定初始化(Designated Initializers)太空梭運算子 <=>範圍 for 迴圈語句支援初始化非型別模板形參支援字串[[likely]], [[unlikely]]日曆(Calendar)和時區(Timezone)功能std::span特性測試宏<version>consteval 函式constinit用 using 引用 enum 型別格式化庫(std::format)增加數學常量std::source_location[[nodiscard(reason)]]位運算一些小更新參考資料新增關鍵字(keywords)conceptrequiresconstinitconstevalco_awaitco_returnco_yieldchar8_t新增識別符號(Identifies)importmodule模組(Modules)優點沒有標頭檔案宣告實現仍然可分離, 但非必要可以顯式指定那些匯出(類, 函式等)不需要標頭檔案重複引入宏 (include guards)模組之間名稱可以相同不會衝突模組只處理一次, 編譯更快 (標頭檔案每次引入都需要處理)預處理宏只在模組內有效模組引入順序無關緊要建立模組
// cppcon.cpp export module cppcon; namespace CppCon { auto GetWelcomeHelper() { return "Welcome to CppCon 2019!"; } export auto GetWelcome() { return GetWelcomeHelper();} }
引用模組
// main.cpp import cppcon; int main(){ std::cout << CppCon::GetWelcome(); }
import 標頭檔案import <iostream>隱式地將 iostream 轉換為模組加速構建, 因為 iostream 只會處理一次和預編譯頭 (PCH) 具有相似的效果Ranges
Ranges 是什麼 ?
Range 代表一串元素, 或者一串元素中的一段類似 begin/end 對好處:
簡化語法和方便使用vector<int> data{11, 22, 33}; sort(begin(data), end(data)); sort(data); // 使用 Ranges
防止 begin/end 不配對使變換/過濾等串聯操作成為可能相關功能
檢視(View): 延遲計算, 不持有, 不改寫Actions: 即時處理(eagerly evaluated), 改寫Algorithms: 所有接受 begin/end 對的演算法都可用Views 和 actions 使用管道符|串聯例子串聯檢視vector<int> data {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; auto result = data | views::remove_if([](int i) { return i % 2 == 1;}) | views::transform([](int i) { return to_string(i);}); // result = {"2", "4", "6", "8", "10" };// 注意 以上操作被延遲, 當你遍歷result的時候才觸發
串聯actions
vector<int> data{4, 3, 4, 1, 8, 0, 8}; vector<int> result = data | actions::sort | actions::unique;
排序然後去重操作會原地對data進行更改, 然後返回過濾和變換int total = accumulate ( view::ints(1) | view::transform([](int i) {return i * i;}) | view::take(10), 0);
view::ints(1) 產生一個無限的整型數列平方取前10個元素, 然後累加(accumulate)所有的計算延遲到accumulate累加遍歷的時候發生協程(Coroutines)什麼是協程它是一個函式具備如下關鍵字之一:co_wait: 掛起協程, 等待其它計算完成co_return: 從協程返回 (協程 return 禁止使用)co_yield: 同 python yield, 彈出一個值, 掛起協程, 下一次呼叫繼續協程的執行for co_await 迴圈體
for co_await (for-range-declaration: expression) statement
用處
簡化如下問題的實現:generator非同步I/O延遲計算事件驅動的程式例子(VC++)experimental::generator<int> GetSequenceGenerator( int startValue, size_t numberOfValues) { for (int i = 0 startValue; i < startValue + numberOfValues; ++i){ time_t t = system_clock::to_time_t(system_clock::now()); cout << std:: ctime(&t); co_yield i; } } int main() { auto gen = GetSequenceGenerator(10, 5); for (const auto& value : gen) { cout << value << "(Press enter for next value)" << endl; cin.ignore(); } }
Concepts對模板類和函式的模板形參的約束編譯期斷言可宣告多個如何定義template<typename T> concept Incrementable = requires(T x) {x++; ++x;};
使用
template<Incrementable T> void Foo(T t); template<typename T> requires Incrementable<T> void Foo(T t); template<typename T> void Foo(T t) requires Incrementable<T>; void Foo(Incrementable auto t);
例子具備size() 方法, 且返回size_t
template <typename T> concept HasSize = requires (T x){ {x.size()} -> std::convertible_to<std::size_t>; };
組合concept
template<typename T> requires Incrementable<T> && Decrementable<T> void Foo(T t); // or template<typename T> concept Incr_Decrementable = Incrementable<T> && Decrementable<T>; template<Incr_Decrementable T> void Foo(T t);
Lambda 表示式的更新[=, this] 需要顯式捕獲this變數C++20 之前 [=] 隱式捕獲thisC++20 開始 需要顯式捕獲this: [=, this]模板形式的 Lambda 表示式可以在lambda表示式中使用模板語法
[]template<T>(T x) {/* ... */}; []template<T>(T* p) {/* ... */}; []template<T, int N>(T (&a)[N]) {/* ... */};
原因1
C++20之前: 獲取 vector 元素型別, 你需要這麼寫auto func = [](auto vec){ using T = typename decltype(vec)::value_type; }
C++20 你可以:
auto func = []<typename T>(vector<T> vec){ // ... }
原因2: 方便獲取通用lambda形參型別, 訪問靜態函式
c++20 以前auto func = [](auto const& x){ using T = std::decay_t<decltype(x)>; T copy = x; T::static_function(); using Iterator = typename T::iterator; }
C++20 開始
auto func = []<typename T>(const T& x){ T copy = x; T::static_function(); using Iterator = typename T::iterator; }
原因3: 完美轉發
pre C++20:auto func = [](auto&& ...args) { return foo(std::forward<decltype(args)>(args)...); }
since C++20
auto func = []<typename …T>(T&& …args){ return foo(std::forward(args)...); }
Lambda 表示式捕獲支援打包展開(Pack Expansion)Pre C++20
template<class F, class... Args> auto delay_invoke(F f, Args... args){ return [f, args...]{ return std::invoke(f, args...); } }
Since C++20
template<class F, class... Args> auto delay_invoke(F f, Args... args){ // Pack Expansion: args = std::move(args)... return [f = std::move(f), args = std::move(args)...](){ return std::invoke(f, args...); } }
常量表達式(constexpr) 的更新constexpr 虛擬函式constexpr 的虛擬函式可以重寫非 constexpr 的虛擬函式非 constexpr 虛擬函式可以重寫 constexpr 的虛擬函式constexpr 函式可以:使用 dynamic_cast() 和 typeid動態記憶體分配更改union成員的值包含 try/catch但是不允許throw語句在觸發常量求值的時候 try/catch 不發生作用需要開啟 constexpr std::vectorconstexpr string & vectorstd::string 和 std::vector 型別現在可以作為 constexpr未來需要支援 constexpr 反射原子(Atomic)智慧指標智慧指標(shared_ptr)執行緒安全嗎?是: 引用計數控制單元執行緒安全, 保證物件只被釋放一次否: 對於資料的讀寫沒有執行緒安全如何將智慧指標變成執行緒安全?使用 mutex 控制智慧指標的訪問使用全域性非成員原子操作函式訪問, 諸如: std::atomic_load(), atomic_store(), …缺點: 容易出錯, 忘記使用這些操作C++20: atomic<shared_ptr<T>>, atomic<weak_ptr<T>>內部原理可能使用了mutex全域性非成員原子操作函式標記為不推薦使用(deprecated)例子template<typename T> class concurrent_stack { struct Node { T t; shared_ptr<Node> next; }; atomic_shared_ptr<Node> head; // C++11: 去掉 "atomic_" 並且在訪問時, 需要用 // 特殊的函式控制執行緒安全, 例如用std::tomic_load public: class reference { shared_ptr<Node> p; <snip> }; auto find(T t) const { auto p = head.load(); // C++11: atomic_load(&head) while (p && p->t != t) p = p->next; return reference(move(p)); } auto front() const { return reference(head); } void push_front(T t) { auto p = make_shared<Node>(); p->t = t; p->next = head; while (!head.compare_exchange_weak(p->next, p)){ } // C++11: atomic_compare_exchange_weak(&head, &p->next, p); } void pop_front() { auto p = head.load(); while (p && !head.compare_exchange_weak(p, p->next)) { } // C++11: atomic_compare_exchange_weak(&head, &p, p->next); } };
例子來自 Herb Sutter 的 N4162 論文
自動合流(Joining), 可中斷(Cancellable) 的執行緒std::jthread標頭檔案 <thread>支援中斷解構函式中自動 Join解構函式呼叫 stop_source.request_stop() 然後 join()中斷執行緒執行標頭檔案 <stop_token>std::stop_token用來查詢執行緒是否中斷可以和condition_variable_any配合使用std::stop_source用來請求執行緒停止執行stop_resources 和 stop_tokens 都可以查詢到停止請求std::stop_callback如果對應的stop_token 被要求終止, 將會觸發回撥函式用法: std::stop_callback myCallback(myStopToken, []{ /* … */ });例子自動合流 Joinstd::thread 在解構函式中如果執行緒 joinable() 會直接呼叫 std::terminate() 直接導致程式退出
void DoWorkPreCpp20() { std::thread job([] { /* ... */ }); try { // ... Do something else ... } catch (...) { job.join(); throw; // rethrow } job.join(); } void DoWork() { std::jthread job([] { /* ... */ }); // ... Do something else ... } // jthread destructor automatically calls join()
中斷std::jthread job([](std::stop_token token) { while (!token.stop_requested()) { //... } }); //... job.request_stop(); // auto source = job.get_stop_source() // auto token = job.get_stop_token()
C++20 同步(Synchronization)庫訊號量(Semaphore), 維基百科請走這裡標頭檔案<semaphore>輕量級的同步原語可用來實現任何其他同步概念, 如: mutex, latches, barriers, …兩種型別:多元訊號量(counting semaphore): 建模非負值資源計數二元訊號量(binary semaphore): 只有一個插孔, 兩種狀態, 最適合實現mutexstd::atomic 等待和通知介面等待/阻塞在原子物件直到其值發生改變, 透過通知函式傳送通知比輪訓(polling)來的更高效方法wait()notify_one()notify_all()鎖存器(Latch)和屏障(Barrier)輔助執行緒條件同步鎖存器(Latches)標頭檔案<latch>執行緒的同步點執行緒將阻塞在這個位置, 直到到達的執行緒個數達標才放行, 放行之後不再關閉鎖存器只會作用一次屏障(Barriers)<barrier>多個階段每個階段中一個參與者執行至屏障點時被阻塞,需要等待其他參與者都到達屏障點, 當到達執行緒數達標之後階段完成的回撥將被執行執行緒計數器被重置開啟下一階段執行緒得以繼續執行std::atomic_ref標頭檔案<atomic>Atomic 引用透過引用訪問變為原子操作, 被引用物件可以為非原子型別其他更新指定初始化(Designated Initializers)struct Data { int anInt = 0; std::string aString; }; Data d{ .aString = "Hello" };
太空梭運算子 <=>正規名稱: 三路比較運算子三路比較結果如下(a <=> b) < 0 // 如果 a < b 則為 true(a <=> b) > 0 // 如果 a > b 則為 true(a <=> b) == 0 // 如果 a 與 b 相等或者等價 則為 true類似於C的strcmp 函式返回-1, 0, 1一般情況: 自動生成所有的比較運算子, 如果物件是結構體則逐個比較, 可以用下面程式碼代替所有的比較運算子auto X::operator<=>(const Y&) = default;高階情況: 指定返回型別(支援6種所有的比較運算子)
示例:
class Point { int x; int y; public: friend bool operator==(const Point& a, const Point& b){ return a.x==b.x && a.y==b.y; } friend bool operator< (const Point& a, const Point& b){ return a.x < b.x || (a.x == b.x && a.y < b.y); } friend bool operator!=(const Point& a, const Point& b) { return !(a==b); } friend bool operator<=(const Point& a, const Point& b) { return !(b<a); } friend bool operator> (const Point& a, const Point& b) { return b<a; } friend bool operator>=(const Point& a, const Point& b) { return !(a<b); } // ... 其他非比較函式 ... }; #include <compare> class Point { int x; int y; public: auto operator<=>(const Point&) const = default; // 比較運算子自動生成 // ... 其他非比較函式 ... };
標準庫型別支援 <=>vector, string, map, set, sub_match, …例如:範圍 for 迴圈語句支援初始化語句switch 語句初始化 (C++17):struct Foo { int value; int result; }; Foo GetData() { return Foo(); } int main() { switch (auto data = GetData(); data.value) { case 1: return data.result; } }
if 語句初始化 (C++17):
struct Foo { int value; int result; }; Foo* GetData() { return new Foo(); } int main() { if (auto data = GetData(); data) { // Use 'data’ } }
現在範圍 for 迴圈同樣支援初始化 (C++20):
struct Foo { std::vector<int> values; }; Foo GetData() { return Foo(); } int main() { for (auto data = GetData(); auto& value : data.values) { // Use 'data’ } }
非型別模板形參支援字串
template<auto& s> void DoSomething() { std::cout << s << std::endl; } int main() { DoSomething<"CppCon">(); }
[[likely]], [[unlikely]]先驗機率指導編譯器最佳化
switch (value) { case 1: break; [[likely]] case 2: break; [[unlikely]] case 3: break; }
日曆(Calendar)和時區(Timezone)功能<choron> 增加日曆和時區的支援只支援公曆(Gregorian calendar)其他日曆也可透過擴充套件加入, 並能和 <chrono> 進行互動初始化 年, 月 日的方法
// creating a year auto y1 = year{ 2019 }; auto y2 = 2019y; // creating a mouthauto m1 = month{ 9 }; auto m2 = September; // creating a day auto d1 = day{ 18 }; auto d2 = 18d;
建立完整的日期
year_mouth_day fulldate1{2019y, September, 18d}; auto fulldate2 = 2019y / September / 18d; year_mouth_day fulldate3{Monday[3]/September/2019}; // Monday[3] 表示第三個星期一
新的事件間隔單位, 類似於秒, 分鐘, …using days = duration<signed interger type of at least 25bits, ratio_multiply<ratio<24>, hours::period>>; using weeks = ...; using mouths = ...; using years = ...;
例子
weeks w{1}; // 1 周 days d{w}; // 將 1 周 轉換成天數
新的時鐘型別, (之前有 system_clock, steady_clock, high_resolution_clock):utc_clock: represents Coordinated Universal Time (UTC), measures time since 00:00:00 UTC, Thursday, 1 January 1970, including leap secondstai_clock: represents International Atomic Time (TAI), measures time since 00:00:00, 1 January 1958, and was offseted 10 seconds ahead of UTC at that date, it does not include leap secondsgps_clock: represents Global Positioning System (GPS) time, measures time since 00:00:00, 6 January 1980 UTC, it does not include leap secondsfile_clock: alias for the clock used for std::filesystem::file_time_type, epoch is unspecified新增system_clock相關的別名template<class Duration> using sys_time = std::chrono::time_point<std::chrono::system_clock, Duration>; using sys_seconds = sys_time<std::chrono::seconds>; using sys_days = sys_time<std::chrono::days>; // 用例: system_clock::time_point t = sys_days{ 2019y / September / 18d }; // date -> time_point auto yearmonthday = year_month_day{ floor<days>(t) }; // time_point -> date
日期 + 事件
auto t = sys_days{2019y/September/18d} + 9h + 35min + 10s; // 2019-09-18 09:35:10 UTC
時區轉換
// Convert UTC to Denver time: zoned_time denver = { "America/Denver", t }; // Construct a local time in Denver: auto t = zoned_time{ "America/Denver", local_days{Wednesday[3] / September / 2019} + 9h }; // Get current local time: auto t = zoned_time{ current_zone(), system_clock::now() };
std::span標頭檔案<span>某段連續資料的”檢視”不持有資料, 不分配和銷燬資料複製非常快, 推薦複製的方式傳參(類似 string_view)不支援資料跨步(stride)可透過執行期確定長度也可編譯器確定長度int data[42]; span<int, 42> a {data}; // fixed-size: 42 ints span<int> b {data}; // dynamic-size: 42 ints span<int, 50> c {data}; // compilation error span<int> d{ ptr, len }; // dynamic-size: len ints
特性測試宏
透過它可以判斷編譯器是否支援某個功能, 例如
語言特性__has_cpp_attribute(fallthrough)__cpp_binary_literals__cpp_char8_t__cpp_coroutines標準庫特性__cpp_lib_concepts__cpp_lib_ranges__cpp_lib_scoped_lock<version>包含 C++ 標準庫版本, 釋出日期, 版權證書, 特性宏等
consteval 函式constexpr 函式可能編譯期執行, 也可以在執行期執行, consteval 只能在編譯器執行, 如果不滿足要求編譯不透過
constinit強制指定以常量方式初始化
const char* GetStringDyn() { return "dynamic init"; } constexpr const char* GetString(bool constInit) { return constInit ? "constant init" : GetStringDyn(); } constinit const char* a = GetString(true); // ✔ constinit const char* b = GetString(false); // ❌
用 using 引用 enum 型別enum class CardTypeSuit { Clubs, Diamonds, Hearts, Spades }; std::string_view GetString(const CardTypeSuit cardTypeSuit) { switch (cardTypeSuit) { case CardTypeSuit::Clubs: return "Clubs"; case CardTypeSuit::Diamonds: return "Diamonds"; case CardTypeSuit::Hearts: return "Hearts"; case CardTypeSuit::Spades: return "Spades"; } } std::string_view GetString(const CardTypeSuit cardTypeSuit) { switch (cardTypeSuit) { using enum CardTypeSuit; // 這裡 case Clubs: return "Clubs"; case Diamonds: return "Diamonds"; case Hearts: return "Hearts"; case Spades: return "Spades"; } }
格式化庫(std::format)不展開, 類似Python 的格式化,
std::string s = std::format("Hello CppCon {}!", 2019);
增加數學常量
再也不用為 M_PI 發愁啦
標頭檔案 <numbers>包含 e, log2e, log10e pi, inv_pi, inv_sqrt pi ln2, ln10 sqrt2, sqrt3, inv_sqrt3 egammastd::source_location用於獲取程式碼位置, 對於日誌和錯誤資訊尤其有用
[[nodiscard(reason)]]表明返回值不可拋棄, 加入理由的支援
[[nodiscard("Ignoring the return value will result in memory leaks.")]] void* GetData() { /* ... */ }
位運算
加入迴圈移位, 計數0和1位等功能
一些小更新字串支援 starts_with, ends_withmap 支援 contains 查詢是否存在某個鍵list 和 forward list 的 remove, remove_if 和 unique 操作返回 size_type 表明刪除個數<algorithm> 增加 shift_left, shift_rightmidpoint 計算中位數, 可避免溢位lerp 線性插值 lerp( float a, float b, float t ) 返回 新的向量化策略 unsequenced_policy(execution::unseq)std::string str = "Hello world!"; bool b = str.starts_with("Hello"); // starts_with, ends_with std::map myMap{ std::pair{1, "one"s}, {2, "two"s}, {3, "three"s} }; bool result = myMap.contains(2); // contains, 再也不用 .find() == .end() 了
參考資料https://www.youtube.com/watch?v=Y652wQqbYEIhttps://www.modernescpp.com/index.php/a-new-thread-with-c-20-std-jthread