C++20 新特性全在这一张图里了
The following article is from 程序喵大人 Author 程序喵大人
目录
语言特性
比较运算<=>
对于 (a <=> b),如果a > b ,则运算结果>0,如果a < b,则运算结果<0,如果a==b,则运算结果等于0,注意下,运算符的结果类型会根据a和b的类型来决定,所以我们平时使用时候最好直接用auto,方便快捷。
#include <compare>
#include <iostream>
int main() {
double foo = 0.0;
double bar = 1.0;
auto res = foo <=> bar;
if (res < 0)
std::cout << "foo is less than bar";
else if (res == 0)
std::cout << "foo and bar are equal";
else if (res > 0)
std::cout << "foo is greater than bar";
}
输出:
foo is less than bar
指定初始化(cppreference中列出来了这项,但是我没搞明白多了啥特性???知道的老铁请打在评论区):
struct A { int x; int y; int z; };
A a{.y = 2, .x = 1}; // error; designator order does not match declaration order
A b{.x = 1, .z = 2};
这种乱序初始化方式在C语言中可以,但是在C++中是不可以的,C++里一定要按顺序初始化。
for循环括号里可以初始化:
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {0, 1, 2, 3, 4, 5};
for (const int& i : v) // access by const reference
std::cout << i << ' ';
std::cout << '\n';
for (auto i : v) // access by value, the type of i is int
std::cout << i << ' ';
std::cout << '\n';
for (auto n = v.size(); auto i : v) // the init-statement (C++20)
std::cout << --n + i << ' ';
std::cout << '\n';
}
多了一个char8_t类型,和普通的char没区别,就是容易区分出具体大小而已,就像int32_t与int一样。
[[no_unique_address]]:看着貌似没啥用,没具体关注...
[[likely]]和[[unlikely]]:在分支预测时,用于告诉编译器哪个分支更容易被执行,哪个不容易执行,方便编译器做优化。
constexpr long long fact(long long n) noexcept {
if (n > 1) [[likely]]
return n * fact(n - 1);
else [[unlikely]]
return 1;
}
lambda表达式的捕获
C++20之前[=]会隐式捕获this,而C++20需要显式捕获,这样[=, this]
struct S2 { void f(int i); };
void S2::f(int i)
{
[=]{}; // OK: by-copy capture default
[=, &i]{}; // OK: by-copy capture, except i is captured by reference
[=, *this]{}; // until C++17: Error: invalid syntax
// since c++17: OK: captures the enclosing S2 by copy
[=, this] {}; // until C++20: Error: this when = is the default
// since C++20: OK, same as [=]
}
lambda表达式可以使用模板:
// generic lambda, operator() is a template with two parameters
auto glambda = []<class T>(T a, auto&& b) { return a < b; };
// generic lambda, operator() is a template with one parameter pack
auto f = []<typename ...Ts>(Ts&& ...ts) {
return foo(std::forward<Ts>(ts)...);
};
consteval
consteval修饰的函数只会在编译期间执行,如果不能编译期间执行,则编译失败。
consteval int f() { return 42; }
constint
断言一个变量有静态初始化,即零初始化和常量初始化,否则程序是有问题的。
const char *g() { return "dynamic initialization"; }
constexpr const char *f(bool p) { return p ? "constant initializer" : g(); }
constinit const char *c = f(true); // OK
// constinit const char *d = f(false); // error
删除了在很多上下文中需要使用typename来消除类型歧义的要求
template<typename IterT>
void workWithIterator(IterT it){
typename std::iterator_traits<IterT>::value_type tmp(*it); // C++20前
std::iterator_traits<IterT>::value_type tmp(*it); // C++20
}
结构体直接初始化:
T object {arg1, arg2, ... }; // C++11
T object { .designator = arg1 , .designator { arg2 } ... }; // C++20
协程
协程具体是什么不做过多介绍,它最大的特点就是可以使用顺序代码的逻辑执行异步的任务,让我们写异步代码非常的方便。
如果一个函数的定义有以下任何一种情况,那么它就是协程:
1. 使用co_await操作符暂停执行,直到恢复
task<> tcp_echo_server() {
char data[1024];
for (;;) {
size_t n = co_await socket.async_read_some(buffer(data));
co_await async_write(socket, buffer(data, n));
}
}
2. 使用关键字co_yield暂停执行,返回一个值
generator<int> iota(int n = 0) {
while(true)
co_yield n++;
}
3. 使用关键字co_return完成执行,返回一个值
lazy<int> f() {
co_return 7;
}
每个协程都必须有一个返回类型来满足以下的许多要求。
示例代码:
#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
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);
}
限制:
协程不能使用可变参数、普通返回语句或占位符返回类型(auto或Concept)。Constexpr函数、构造函数、析构函数和主函数不能是协程。
Modules:直接看代码
// helloworld.cpp
export module helloworld; // module declaration
import <iostream>; // import declaration
export void hello() { // export declaration
std::cout << "Hello world!\n";
}
// main.cpp
import helloworld; // import declaration
int main() {
hello();
}
modules使用方式和include差不多,但modules使用比include头文件速度更快,C++全球开发者大会中,C++之父贴出来过测试数据,modules效率比include高了25倍。
using 可以引用enum
enum class Animal {
kCat,
kGog
};
int main() {
Animal animal;
using enum Animal;
switch(animal) {
case kCat:
break;
}
}
Constraints and concepts约束和概念
类模板、函数模板和非模板函数(通常是类模板的成员)可以与一个约束相关联,这个约束指定了对模板实参的要求,这些实参可用于选择最合适的函数重载和模板特化。
这些需求的命名被称为概念。每个概念都是一个谓词,在编译时计算,并成为模板接口的一部分,在那里它被用作约束:
#include <string>
#include <cstddef>
#include <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
}
缩写函数模板
void f1(auto); // same as template<class T> void f(T)
void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept
void f3(C2 auto...); // same as template<C2... Ts> void f3(Ts...), if C2 is a concept
void f4(const C3 auto*, C4 auto&); // same as template<C3 T, C4 U> void f4(const T*, U&);
template <class T, C U>
void g(T x, U y, C auto z); // same as template<class T, C U, C W> void g(T x, U y, W z);
新的library特性
std::format系列
std::format可用于替代printf,它的目的是补充现有的c++ I/O流库,并重用它的一些基础设施,如用户定义类型的重载插入操作符。
std::string message = std::format("The answer is {}.", 42);
增加日历和时区的支持
增加了std::atomic:让智能指针线程安全
source_location:可作为__LINE__ 、__func__这些宏的替代:
#include <iostream>
#include <string_view>
#include <source_location>
void log(const std::string_view message,
const std::source_location& location = std::source_location::current())
{
std::cout << "info: "
<< location.file_name() << "("
<< location.line() << ":"
<< location.column() << ") `"
<< location.function_name() << "` "
<< message << '\n';
}
int main(int, char*[])
{
log("Hello world!");
}
span:类模板span可以表示一个片段。
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <span>
template<class T, std::size_t N> [[nodiscard]]
constexpr auto slide(std::span<T,N> s, std::size_t offset, std::size_t width) {
return s.subspan(offset, offset + width <= s.size() ? width : 0U);
}
template<class T, std::size_t N, std::size_t M> [[nodiscard]]
constexpr bool starts_with(std::span<T,N> data, std::span<T,M> prefix) {
return data.size() >= prefix.size()
&& std::equal(prefix.begin(), prefix.end(), data.begin());
}
template<class T, std::size_t N, std::size_t M> [[nodiscard]]
constexpr bool ends_with(std::span<T,N> data, std::span<T,M> suffix) {
return data.size() >= suffix.size()
&& std::equal(data.end() - suffix.size(), data.end(),
suffix.end() - suffix.size());
}
template<class T, std::size_t N, std::size_t M> [[nodiscard]]
constexpr bool contains(std::span<T,N> span, std::span<T,M> sub) {
return std::search(span.begin(), span.end(), sub.begin(), sub.end())
!= span.end();
}
void print(const auto& seq) {
for (const auto& elem : seq) std::cout << elem << ' ';
std::cout << '\n';
}
int main()
{
constexpr int a[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
constexpr int b[] { 8, 7, 6 };
for (std::size_t offset{}; ; ++offset) {
constexpr std::size_t width{6};
auto s = slide(std::span{a}, offset, width);
if (s.empty())
break;
print(s);
}
static_assert(starts_with(std::span{a}, std::span{a,4})
&& starts_with(std::span{a+1, 4}, std::span{a+1,3})
&& !starts_with(std::span{a}, std::span{b})
&& !starts_with(std::span{a,8}, std::span{a+1,3})
&& ends_with(std::span{a}, std::span{a+6,3})
&& !ends_with(std::span{a}, std::span{a+6,2})
&& contains(std::span{a}, std::span{a+1,4})
&& !contains(std::span{a,8}, std::span{a,9}));
}
endian:可获取当前平台是大端序还是小端序
#include <bit>
#include <iostream>
int main() {
if constexpr (std::endian::native == std::endian::big) {
std::cout << "big-endian" << '\n';
}
else if constexpr (std::endian::native == std::endian::little) {
std::cout << "little-endian" << '\n';
}
else {
std::cout << "mixed-endian" << '\n';
}
}
make_shared 支持构造数组
std::remove_cvref看名字就知道,去除CV,去除引用
#include <iostream>
#include <type_traits>
int main()
{
std::cout << std::boolalpha
<< std::is_same_v<std::remove_cvref_t<int>, int> << '\n'
<< std::is_same_v<std::remove_cvref_t<int&>, int> << '\n'
<< std::is_same_v<std::remove_cvref_t<int&&>, int> << '\n'
<< std::is_same_v<std::remove_cvref_t<const int&>, int> << '\n'
<< std::is_same_v<std::remove_cvref_t<const int[2]>, int[2]> << '\n'
<< std::is_same_v<std::remove_cvref_t<const int(&)[2]>, int[2]> << '\n'
<< std::is_same_v<std::remove_cvref_t<int(int)>, int(int)> << '\n';
}
结果全是true
std::to_address:获得由p表示的地址,而不形成对p所指向的对象的引用。
线程同步:
barrier:屏障
latch:CountDownLatch
counting_semaphore:信号量
std::jthread:之前的std::thread在析构时如果没有join或者detach会crash,而jthread在析构时会自动join。jthread也可以取消线程:request_stop()。
C++20也引进了一些中断线程执行的相关类:
stop_token:查询线程是否中断
stop_source:请求线程停止运行
stop_callback:stop_token执行时,可以触发的回调函数
basic_osyncstream:它是对std::basic_syncbuf的再包装,直接使用std::cout多线程下可能出现数据交叉,osyncstream不会发生这种情况。
{
std::osyncstream synced_out(std::cout); // synchronized wrapper for std::cout
synced_out << "Hello, ";
synced_out << "World!";
synced_out << std::endl; // flush is noted, but not yet performed
synced_out << "and more!\n";
} // characters are transferred and std::cout is flushed
string的系列操作
string::starts_with
string::ends_with
string_view::starts_with
string::view::ends_with
#include <iostream>
#include <string_view>
#include <string>
template <typename PrefixType>
void test_prefix_print(const std::string& str, PrefixType prefix)
{
std::cout << '\'' << str << "' starts with '" << prefix << "': " <<
str.starts_with(prefix) << '\n';
}
int main()
{
std::boolalpha(std::cout);
auto helloWorld = std::string("hello world");
test_prefix_print(helloWorld, std::string_view("hello"));
test_prefix_print(helloWorld, std::string_view("goodbye"));
test_prefix_print(helloWorld, 'h');
test_prefix_print(helloWorld, 'x');
}
std::assume_aligned
template< std::size_t N, class T >
[[nodiscard]] constexpr T* assume_aligned(T* ptr);
指定多少字节对齐,来进一步生成有效的代码。
void f(int* p) {
int* p1 = std::assume_aligned<256>(p);
// Use p1, not p, to ensure benefit from the alignment assumption.
// However, the program has undefined behavior if p is not aligned
// regardless of whether p1 is used.
}
bind_front:和使用std::bind绑定第一个参数效果相同
std::ssize:signed size
#include <iostream>
#include <vector>
#include <iterator>
int main()
{
std::vector<int> v = { 3, 1, 4 };
std::cout << std::size(v) << '\n';
int a[] = { -5, 10, 15 };
std::cout << std::size(a) << '\n';
// since C++20 the signed size (ssize) can avail
auto i = std::ssize(v);
for (--i; i != -1; --i) {
std::cout << v[i] << ' ';
}
std::cout << "\n" "i = " << i << '\n';
}
midpoint 函数计算中位数
lerp函数计算线性差值:
constexpr double lerp( double a, double b, double t) noexcept {
return a + t * (b - a);
}
Ranges库:ranges库提供了用于处理元素范围的组件,包括各种视图适配器。表示连续元素或者连续元素的片段。
#include <ranges>
#include <iostream>
int main()
{
auto const ints = {0,1,2,3,4,5};
auto even = [](int i) { return 0 == i % 2; };
auto square = [](int i) { return i * i; };
// "pipe" syntax of composing the views:
for (int i : ints | std::views::filter(even) | std::views::transform(square)) {
std::cout << i << ' ';
}
std::cout << '\n';
// a traditional "functional" composing syntax:
for (int i : std::views::transform(std::views::filter(ints, even), square)) {
std::cout << i << ' ';
}
}
输出:
0 4 16
0 4 16
std::is_bounded_array:检查数组是不是有界
std::is_unbounded_array
#include <iostream>
#include <type_traits>
class A {};
int main()
{
std::cout << std::boolalpha;
std::cout << std::is_bounded_array_v<A> << '\n';
std::cout << std::is_bounded_array_v<A[]> << '\n';
std::cout << std::is_bounded_array_v<A[3]> << '\n';
std::cout << std::is_bounded_array_v<float> << '\n';
std::cout << std::is_bounded_array_v<int> << '\n';
std::cout << std::is_bounded_array_v<int[]> << '\n';
std::cout << std::is_bounded_array_v<int[3]> << '\n';
}
在numbers头文件中定义了一些数学常量:
e
log2e
log10e
pi
inv_pi
inv_sqrtpi
ln2
ln10
sqrt2
sqrt3
inv_sqrt3
egamma
phi
贴一张cppreference的截图:
参考资料
https://en.cppreference.com/w/cpp/20
- EOF -
关注「程序员的那些事」加星标,不错过圈内事
点赞和在看就是最大的支持❤️