C++之父都說過,C++20是C++語言的一次重大變革,引入了大量的新特性。
這其中個人認為最重要新特性是這三個:
Modules
Coroutines
Concepts
Modules
直接看代碼:
// helloworld.ixx
export module helloworld; // module declaration
import <iostream>; // import declaration
export void hello() { // export declaration
std::cout << "Hello world!\n";
}
// main.cpp\n import helloworld; // import declaration
int main() {
hello();
}
每個C++開發者應該都知道include方式是將header中的代碼拷貝一份到源文件中,在大的工程項目中還有很多冗余的include,種種原因,導致編譯速度相當的慢,而modules卻大大改善了這種問題。
modules使用方式和include差不多,但modules使用比include頭文件速度更快,C++全球開發者大會中,C++之父貼出來過測試數據,modules效率比include高了25倍。
以后modules肯定會是主流使用方式。
Coroutines
協程是一種比線程和進程更高效的多任務處理模型。
在C++20中,終于把協程引了進來,協程具體還分為有棧協程和無棧協程,兩者對比,無棧協程是更高效的協程。
而C++20支持的就是無棧協程,為此提供了三個關鍵字:
co_await:暫停執行,直到恢復
co_yield:暫停執行,返回一個值
co_return:完成執行,返回一個值
這是一段cppreference上,協程相關的示例代碼:
auto switch_to_new_thread(std::jthread& out) {
struct awaitable {
std::jthread* p_out;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h) {
std::jthread& out = *p_out;
if (out.joinable())
throw std::runtime_error("Output jthread parameter not empty");
out = std::jthread([h] { h.resume(); });
// Potential undefined behavior: accessing potentially destroyed *this
// std::cout << "New thread ID: " << p_out->get_id() << '\n';
std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
}
void await_resume() {}
};
return awaitable{&out};
}
struct task{
struct promise_type {
task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
task resuming_on_new_thread(std::jthread& out) {
std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
co_await switch_to_new_thread(out);
// awaiter destroyed here
std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
int main() {
std::jthread out;
resuming_on_new_thread(out);
}
目前C++20只是從語法層面對協程做了支持,還沒有相關的協程庫,要想使用協程,還需要引入協程庫,但不可否認,C++20已經支持了協程,以后在這方面肯定會越來越完善。
前一段時間參加的C++全球開發者大會,C++之父也說過會全力在C++23標準中引入對協程庫的支持。
Concepts
Concepts在模板編程中起到重大的作用, 類模板、函數模板和非模板函數(通常是類模板的成員)可以與一個約束相關聯,這個約束指定了對模板實參的要求,這些實參可用于選擇最合適的函數重載和模板特化。
很多人應該都知道SFINAE,在C++20前多數都在使用std::enable_if,這相當的麻煩,代碼可讀性也不高,編譯器報錯信息也不是很友好,而有了Concepts就方便的多, 每個Concepts都是一個謂詞,在編譯時計算,并成為模板接口的一部分,在那里它被用作約束:
template<typename T>
concept Hashable = requires(T a) {
{ std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};
struct meow {};
// Constrained C++20 function template:
template<Hashable T>
void f(T) {}
int main() {
using std::operator""s;
f("abc"s); // OK, std::string satisfies Hashable
//f(meow{}); // Error: meow does not satisfy Hashable
}
其實C++20還有一些特性我比較喜歡,比如std::jthread,真真切切的解決了std::thread存在的舊有問題。
還有fmt,因為cout方式輸出復雜的格式很麻煩:比如我想輸出a + b = c:
std::cout << a << " + " << b << " = " << c << std::endl;
是不是很麻煩,那可以使用printf?
printf("%d + %d = %d \n", a, b, c);
但printf需要開發者填入變量格式,如果填寫有誤,有可能打印錯誤,也有可能crash:
某個long long 型,使用%d打印會怎么樣?
某個int32_t型,使用%lld打印會怎么樣?
某個const char* 型,使用%d打印會怎么樣?
某個float型,使用%d打印會怎么樣?
所以使用printf會給開發者帶來負擔,一定要在打印前確認變量的類型,并且一定要保證配置的輸出格式正確,我想大多數人可能都會在print某個變量前搜索過打印某個類型需要使用哪種格式吧?
那用啥呢?cout配合format。
std::cout << std::format("{} + {} = {} \n", a, b, c);
C++20其實還有很多有意思的新特性,而這些特性在這本書中都有詳細的介紹,大家感興趣可以看看。