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其实还有很多有意思的新特性,而这些特性在这本书中都有详细的介绍,大家感兴趣可以看看。