C++20: Bộ tứ lớn và hơn thế nữa
Khám phá các tính năng biến đổi của C++20: khái niệm, phạm vi, toán tử tàu vũ trụ, std::format, bộ khởi tạo được chỉ định, std::span, v.v.
C++20: Bước nhảy vọt thế hệ
C++20 là bản cập nhật quan trọng nhất đối với C++ kể từ C++11. Nó giới thiệu bốn tính năng chính — Khái niệm, Phạm vi, Coroutines và Mô-đun — mỗi tính năng này về cơ bản thay đổi cách viết mã C++. Ngoài "Big Four", C++20 còn mang đến nhà điều hành tàu vũ trụ,std::format,std::span, các công cụ khởi tạo được chỉ định và nhiều cải tiến constexpr. Bài học này đề cập đến mọi thứ ngoại trừ Coroutine và Mô-đun có các bài học riêng.
Các khái niệm: Các mẫu ràng buộc
Các khái niệm thay thế SFINAE bằng các ràng buộc có thể đọc được, có thể kết hợp được trên các tham số mẫu. Chúng tạo ra các thông báo lỗi rõ ràng khi các ràng buộc không được thỏa mãn:
#include <iostream>
#include <concepts>
#include <string>
#include <vector>
#include <numeric>
// Define a custom concept
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
// Use concept in a requires clause
template<typename T>
requires Numeric<T>
T add(T a, T b) {
return a + b;
}
// Shorthand syntax — concept as type constraint
auto multiply(Numeric auto a, Numeric auto b) {
return a * b;
}
// Concept with multiple requirements
template<typename Container>
concept Summable = requires(Container c) {
{ c.begin() } -> std::input_or_output_iterator;
{ c.end() } -> std::input_or_output_iterator;
{ c.size() } -> std::convertible_to<std::size_t>;
};
template<Summable Container>
auto sum_container(const Container& c) {
using T = typename Container::value_type;
return std::accumulate(c.begin(), c.end(), T{});
}
int main() {
std::cout << add(3, 4) << "\n"; // 7
std::cout << multiply(2.5, 4.0) << "\n"; // 10.0
std::vector<int> v{1, 2, 3, 4, 5};
std::cout << sum_container(v) << "\n"; // 15
// add(std::string("a"), std::string("b")); // Compile error!
// Error message: constraints not satisfied — Numeric<std::string> is false
return 0;
}Phạm vi: Thuật toán tổng hợp
Các dãy mang lại cách tiếp cận theo kiểu đường ống, chức năng cho STL. Chế độ xem lười biếng - chúng không phân bổ hoặc sao chép dữ liệu, soạn thảo các phép biến đổi chỉ thực hiện khi được lặp lại:
#include <iostream>
#include <ranges>
#include <vector>
#include <string>
#include <algorithm>
int main() {
std::vector<int> numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Pipeline: filter even, square them, take first 3
auto result = numbers
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; })
| std::views::take(3);
for (int n : result) {
std::cout << n << " "; // 4 16 36
}
std::cout << "\n";
// Range-based algorithms with projections
struct Employee {
std::string name;
int salary;
};
std::vector<Employee> team{
{"Alice", 90000}, {"Bob", 75000}, {"Carol", 120000}
};
// Sort by salary using a projection — no custom comparator needed
std::ranges::sort(team, {}, &Employee::salary);
for (const auto& e : team) {
std::cout << e.name << ": " << e.salary << "\n";
}
// iota generates an infinite sequence
for (int i : std::views::iota(1) | std::views::take(5)) {
std::cout << i << " "; // 1 2 3 4 5
}
std::cout << "\n";
return 0;
}So sánh ba chiều và công cụ khởi tạo được chỉ định
Toán tử tàu vũ trụ<=>tạo ra tất cả sáu toán tử so sánh từ một khai báo. Trình khởi tạo được chỉ định đưa khởi tạo trường được đặt tên từ C sang C++ với tính năng an toàn bổ sung:
#include <iostream>
#include <compare>
#include <string>
struct Version {
int major;
int minor;
int patch;
// One operator generates ==, !=, <, >, <=, >=
auto operator<=>(const Version&) const = default;
};
struct Config {
std::string host = "localhost";
int port = 8080;
bool ssl = false;
int max_connections = 100;
};
int main() {
Version v1{2, 0, 0};
Version v2{1, 9, 5};
if (v1 > v2) std::cout << "v1 is newer\n"; // true
if (v1 != v2) std::cout << "versions differ\n";
// Strong ordering gives you all 6 operators for free
auto cmp = v1 <=> v2;
if (cmp > 0) std::cout << "v1 > v2\n";
// Designated initializers — name the fields you're setting
Config cfg{
.host = "example.com",
.port = 443,
.ssl = true
// .max_connections keeps its default value (100)
};
std::cout << cfg.host << ":" << cfg.port
<< " SSL=" << std::boolalpha << cfg.ssl
<< " max=" << cfg.max_connections << "\n";
return 0;
}std::format & std::span
std::formatmang định dạng chuỗi giống Python vào C++.std::spanlà chế độ xem không sở hữu đối với một chuỗi phần tử liền kề, thay thế các cặp con trỏ thô + kích thước:
#include <iostream>
#include <format>
#include <span>
#include <vector>
#include <array>
// std::span — works with any contiguous container
void print_values(std::span<const int> data) {
for (int v : data) {
std::cout << std::format("{:>5}", v);
}
std::cout << "\n";
}
int main() {
// std::format — type-safe, positional formatting
std::string msg = std::format("Hello, {}! You are {} years old.", "Alice", 30);
std::cout << msg << "\n";
// Format specifiers
std::cout << std::format("Hex: {:#x}, Oct: {:#o}, Bin: {:#b}\n", 255, 255, 255);
std::cout << std::format("Pi: {:.4f}\n", 3.14159265);
std::cout << std::format("{:<15} {:>10}\n", "Name", "Score");
std::cout << std::format("{:<15} {:>10}\n", "Alice", 95);
// std::span — unifies arrays, vectors, and raw buffers
int c_array[] = {1, 2, 3, 4, 5};
std::vector<int> vec{6, 7, 8, 9, 10};
std::array<int, 3> arr{11, 12, 13};
print_values(c_array); // works with C array
print_values(vec); // works with vector
print_values(arr); // works with std::array
// Subspan — slice without copying
std::span<int> full(vec);
auto first_three = full.subspan(0, 3);
print_values(first_three); // 6 7 8
return 0;
}Constexpr Cải tiến & Chrono
C++20 mở rộng đáng kể những gì có thể làconstexpr: các hàm ảo,dynamic_cast,try-catchvà thậm chístd::vectorvàstd::stringgiờ đây có thể được sử dụng trong ngữ cảnh constexpr. Điều này có nghĩa là sẽ có nhiều bước tính toán hơn để biên dịch thời gian, dẫn đến mã thời gian chạy nhanh hơn.
Thư việncó hệ thống lịch và múi giờ đầy đủ. Bạn có thể biểu thị các ngày như2024y/January/15, tính toán lịch và chuyển đổi giữa các múi giờ — tất cả đều an toàn về loại và không cần thư viện bên ngoài.
Thuộc tính:[[likely]]và[[unlikely]]gợi ý cho trình biên dịch các nhánh nào là đường dẫn nóng, cho phép tạo mã tốt hơn trong các phần quan trọng về hiệu suất.
Bạn không cần phải sử dụng tất cả C++20 cùng một lúc. Bắt đầu với các tính năng giúp tăng năng suất cao nhất với ít gián đoạn nhất:
1. Toán tử tàu vũ trụ — thêmauto operator<=>(const T&) const = default;vào loại của bạn ngay lập tức
2. Khái niệm — sử dụng các khái niệm tiêu chuẩn (std::integral,std::ranges::range) trước khi viết các khái niệm tùy chỉnh
3. std::format — thay thế định dạngprintfvàstringstream
4. Phạm vi — bắt đầu bằngstd::views::filtervàstd::views::transformtrong mã mới
5. Công cụ khởi tạo được chỉ định — sử dụng cho các cấu trúc cấu hình và loại tùy chọn
Lưu Coroutine và Mô-đun khi nhóm và chuỗi công cụ của bạn đã sẵn sàng.
- Các khái niệm thay thế SFINAE bằng các ràng buộc mẫu có thể đọc được, có thể tổng hợp và xóa các thông báo lỗi
- Phạm vi cung cấp các quy trình tổng hợp, lười biếng bằng cách sử dụng toán tử
|— không cần vùng chứa tạm thời - Toán tử tàu vũ trụ
<=>tạo ra tất cả sáu toán tử so sánh từ một khai báo mặc định std::formatmang đến định dạng chuỗi kiểu Python, an toàn cho C++std::spanlà chế độ xem không sở hữu trên bộ nhớ liền kề, thay thế các cặp con trỏ + kích thước- Trình khởi tạo được chỉ định (
{.field = value}) giúp tự ghi lại cấu trúc xây dựng
Câu đố — Kiểm tra kiến thức
(20 XP)1. `auto operator<=>(const MyType&) const = default;`làm gì?
2. Lợi ích chính của chế độ xem Phạm vi (như`std::views::filter`) là gì?
3. Khái niệm C++20 giải quyết vấn đề gì?