热门问题
时间线
聊天
视角
C++20
2020版C++編程語言標準 来自维基百科,自由的百科全书
Remove ads
C++20,是繼C++17之後的C++程式語言的ISO/IEC標準修訂版的名稱。[1]2020年2月,該標準在布拉格的會議上由WG21進行了技術定稿[2]。同年9月4日草案獲得批准後,C++20同年12月正式發布。[3]相比 C++17,C++20引入了新的語言特性,如概念、模組、運算子「<=>」、協程、指定初始化、新標準屬性等。C++20庫標準還加入了範圍、特性測試宏和位元運算等。
特性改動
自C++11之後標準引入了大量的C++語言、庫特性,在20標準前為了區分這些特性是否生效只能判斷C++標準。20標準為這些語言和程式庫的功能特性定義了一組預處理器宏,使之成為檢測這些功能特性是否存在的一種簡單且可移植的方式。測試宏展開會得到該語言、庫特性添加到標準草案中的年份和月份,如果該特性有顯著變更,宏展開的時間也為更新。
- 屬性測試宏
__has_cpp_attribute( 属性记号 )
宏函式,用以檢測屬性是否支援,如:
#if __has_cpp_attribute(nodiscard) > 201603L
#pragma message("nodiscard version is c++20")
#endif
- 語言特性測試宏
用以檢測當前某個語言功能特性是否支援,單個宏,如:
#if __cpp_concepts >= 201907L
#pragma message("support concepts")
#endif
- 標準庫特性測試宏
用以檢測當前某個標準庫特性是否支援,單個宏,不由編譯器預定義,由<version>標頭檔定義:
#ifdef __cpp_lib_bitops
#pragma message("support bitops")
#endif
新增三路比較運算子,又稱spaceship operator,其形式為:
左操作数 <=> 右操作数
表達式返回一個對象,使得
- 如果 a < b,那麼 (a <=> b) < 0
- 如果 a > b,那麼 (a <=> b) > 0
- 而如果 a 和 b 相等/等價,那麼 (a <=> b) == 0。
三路比較運算子會作為< <= > >=四個運算子的重寫候選,若決議選擇了帶參數順序的operator<=>,則對於操作如x @ y,執行 x <=> y @ 0,對於不帶參數順序的執行 0 @ x <=> y。
新增可以將比較運算子顯式偏好設定=default來要求編譯器為某個類生成對應比較,比如:
struct Point
{
int x;
int y;
auto operator<=>(const Point&) const = default;
};
Remove ads
聚合體初始化的語法糖,在c++11的聚合體初始化基礎上,增加了可以指派具體值的語法:
struct U {
int a;
float b;
};
U u1{ 1, 2.0 };
U u2{ .a = 1, .b = 2.0 };
17標準中給if和switch語句加了初始化語句,20標準則給基於範圍的for加了初始化語句:
std::initializer_list<int> il{ 1, 2, 3 };
for (size_t index{ 0 }; auto& i : il) {
std::cout << std::format("index {} value is {}", index++, i) << std::endl;
}
新增了基礎類型char8_t用以表示UTF8字元,與11標準中的char16_t、char32_t一樣同為語言關鍵字,char8_t的出現主要是為了和舊有的char區分,專門用於表示utf8字元。相對應的標準庫`<string>`增加了std::u8string的別名,以下來自gcc13:
#if __cplusplus >= 201703L && _GLIBCXX_USE_CXX11_ABI
#include <bits/memory_resource.h>
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
namespace pmr {
template<typename _CharT, typename _Traits = char_traits<_CharT>>
using basic_string = std::basic_string<_CharT, _Traits,
polymorphic_allocator<_CharT>>;
using string = basic_string<char>;
#ifdef _GLIBCXX_USE_CHAR8_T
using u8string = basic_string<char8_t>;
#endif
using u16string = basic_string<char16_t>;
using u32string = basic_string<char32_t>;
using wstring = basic_string<wchar_t>;
} // namespace pmr
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
#endif // C++17
Remove ads
該屬性適用於非位域非靜態資料成員,指示編譯器可以最佳化當前成員使其與其他非靜態資料成員重疊,減少主記憶體占用。如果該成員為空類型(不具有資料成員),則編譯器最佳化為不占空間;如果該成員不為空,則其尾隨填充空間可被其他資料成員占用。兩個場景例子:
struct Empty {};
struct WithEmpty {
int32_t x; // 4字节
Empty e; // 1字节(填充至对齐)
}; // 总大小8(4+1+3填充)
struct WithEmptyAttri {
int32_t x; // 4字节
[[no_unique_address]] Empty e; // 优化为不占空间
}; // 总大小4字节
struct NonEmpty {
int32_t x; // 4 字节
[[no_unique_address]] char y; // 1字节,无法优化,仍需对齐,*注意这里指定了no_unique_address
// 3 字节填充
}; // 总大小8字节
struct WithNonEmpty {
NonEmpty ne; // 8 字节,未指定no_unique_address,z不可复用ne空间
char z; // 1字节, 需对齐
// 3 字节填充
}; // 总大小12字节
struct Optimized {
[[no_unique_address]] NonEmpty ne; // 8 字节
char z; // 可以复用 ne 的尾随填充
};
int main() {
static_assert(sizeof(WithEmpty) == 8);
static_assert(sizeof(WithEmptyAttri) == 4);
static_assert(sizeof(NonEmpty) == 8);
static_assert(sizeof(WithNonEmpty) == 12);
static_assert(sizeof(Optimized) == 8);
WithNonEmpty wn;
assert(&wn.z == &wn.ne.y + 4);
Optimized o;
assert(&o.z == &o.ne.y + 1);
return 0;
}
現代cpu有指令預取和分支預測功能,在gcc以往也有__builtin_expect,相對應的20標準新增了likely和unlikely屬性作為c++標準的分支預測最佳化屬性,用於給程式設計師協助編譯器完成分支預測。
可以顯式聲明模板形參用以表示當前為泛型lambda,如:
auto print = []<typename T>(const T &t) { /*...*/ };
template <typename... Args>
auto FactoryByValue(Args&&... args) {
return [... args = std::forward<Args>(args)]() {
((std::cout << "Value:" << args << " Address:" << &args << std::endl), ...);
};
}
template <typename... Args>
auto FactoryByRef(Args&&... args) {
return [&... args = std::forward<Args>(args)]() {
((std::cout << "Value:" << args << " Address:" << &args << std::endl), ...);
};
}
以上代碼中分別按值和按參照擷取了參數包,並通過一元右摺疊(c++17功能)打開了參數包。
20起須顯式聲明this的擷取方式,如下代碼將被編譯器告警:
struct A {
void Test() {
auto f = [=]() { cout << this << endl; };
}
};
模板聲明中typename的使用被簡化,以下場景中無須再使用typename:
struct Data {
using ValueType = int32_t;
using ValuePointer = int32_t*;
};
template <typename T>
struct Foo {
using Type = T::ValueType;
typedef T::ValuePointer Pointer;
T::ValueType val_;
T::ValueType Get();
auto Get(size_t index) -> T::ValuePointer;
template <typename U = T::ValuePointer>
void Set(U u);
};
新增關鍵字consteval,只可用於修飾函式,consteval修飾的函式必須是編譯期執行:
consteval int sqr(int x) {
return x * x;
}
constinit關鍵字僅可用於變數,表明該變數擁有靜態初始化,即該變數的初值為編譯期常數,亦即const initialize,僅可用於靜態儲存期和執行緒儲存期的變數。
const char* g() { return "动态初始化"; }
constexpr const char* f(bool p) { return p ? "常量初始化器" : g(); }
constinit const char* c = f(true); // OK
// constinit const char* d = f(false); // 错误
- constepxr函式可以使用try catch,但異常不允許出現在編譯時:
constexpr int Divide(int a, int b) {
if (b == 0)
throw std::runtime_error("Division by zero");
return a / b;
}
constinit int i = Divide(1, 2); // 正常编译期常量
//constinit int j = Divide(1, 0); // 编译报错,编译期不允许出现异常
- 支援在constexpr函式內使用new、delete動態分配主記憶體,如果是編譯期執行則主記憶體不能流出編譯期之外:
constexpr int* CreateArray(int size) {
int* arr = new int[size];
return arr;
}
constexpr void ReleaseArray(int* p) {
delete[] p;
}
constexpr int Sum(int n) {
int* p = CreateArray(n);
std::iota(p, p + n, 1); // constexpr since C++20
auto t = std::accumulate(p, p + n, 0); // constexpr since C++20
ReleaseArray(p);
return t;
}
constinit int i = Sum(10); // 正常编译期计算累计值55
// constinit int* p = CreateArray(100); // 编译出错,编译期内存不能出现在运行期
int* ap = CreateArray(100); // constexpr函数可以是运行期函数
- constexpr編譯期函式可以是虛擬函式:
class Parent {
public:
constexpr virtual ~Parent() = default;
constexpr virtual int GetID() {
return 0;
}
};
class Child : public Parent {
public:
constexpr virtual ~Child() = default;
constexpr int GetID() override {
return 1;
}
};
constinit int i = Parent().GetID();
constinit int j = Child().GetID();
- constexpr函式允許修改union活躍成員:
union Test {
int i;
int j;
};
constexpr int Foo()
{
Test t{.i = 1};
t.j = 2;
return t.j;
}
constinit int i = Foo();
- 標準庫增加constexpr使用:
#include <vector>
#include <string>
#include <algorithm>
constexpr int Foo() {
std::vector<int> v;
v.emplace_back(9);
v.emplace_back(99);
v.emplace_back(123); // vector::emplace_back 20起为constexpr
std::sort(v.begin(), v.end()); // std::sort 20起为constexpr
// std::remove_if 20起为constexpr
const auto erase_start =
std::remove_if(v.begin(), v.end(), [](auto& i) { return i < 100; } );
v.erase(erase_start, v.end()); // vector::erase 20起为constexpr
std::string n = "Joshua";
return v.size() + n.size(); // 两个size 20起都为constexpr
}
constinit int i = Foo();
20標準起有符號整數必須以二補數表示,N位有符號整數的取值範圍也就固定為-(2N-1-1)到2N-1-1。
規範了左移、右移操作行為:
- 左移即是逐位左移並捨棄移出目標類型的位
- 右移即是算術右移
#include <iostream>
struct U {
int a;
float b;
};
int main()
{
U u(1,2);
std::cout << u.a << " " << u.b << std::endl;
}
函式模板可以省略`template<>`聲明進行簡寫:
void f1(auto t) {} // => template<class T> void f1(T);
void f2(const auto& t, auto u) {} // => template<class T, class U> void f2(const T&, U)
void f3(std::integral auto) {} // => template<std::integral T> void f3(T)
void f4(std::integral auto&...) {} // => template<std::integral... Ts> void f4(Ts&...)
int a[]{ 1, 2, 3, 4 }; // 11标准后编译通过
int* pa = new int[]{ 1, 2, 3, 4 }; // 20标准前无法通过编译,20后可以
標準規定在有列表初始化的情況下,可以通過列表個數來確定陣列大小。
注釋
另見
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads