constexpr表示该函数可以在编译期间被计算,这样可以避免运行期间的性能瓶颈。
使用模板类Array可以实现泛型编程,使得该类型可以适用于不同的数据类型和大小。
#include
// 定义一个模板类 Array,表示一个静态数组类型,能够容纳任何具有固定大小的数据类型
template <typename T, int size>
struct Array {
T data[size]; // 定义数据成员 data,即数组
// 构造函数,用于初始化成员 data
constexpr Array()
: data{} {}
// 重载索引运算符,用于直接访问成员 data 中的元素
constexpr T& operator[](int index) {
return data[index];
}
// 同上,但返回值为 const 引用,用于只读访问
constexpr const T& operator[](int index) const {
return data[index];
}
};
// 使用模板类 Array 创建一个整型数组类型 IntArray,并定义一个 constexpr 函数 fib,用于计算斐波那契数列
constexpr int size = 10; // 数组大小
using IntArray = Array<int, size>;
// 编译期计算斐波那契数列,返回一个 IntArray 对象
constexpr IntArray fib() {
IntArray result{}; // 创建一个 IntArray 对象,并初始化为0
result[0] = 0; // 将第一个元素赋值为 0
result[1] = 1; // 将第二个元素赋值为 1
for (int i = 2; i < size; ++i) { // 迭代计算斐波那契数列
result[i] = result[i - 1] + result[i - 2];
}
return result; // 返回计算结果
}
int main() {
constexpr IntArray result = fib(); // 通过调用 fib 函数,并将返回值赋值给 result 常量数组
for (int i = 0; i < size; ++i) { // 遍历数组,并输出结果
std::cout << result[i] << " ";
}
std::cout << std::endl;
return 0;
}
#include
#include
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor called" << std::endl; }
~MyClass() { std::cout << "MyClass destructor called" << std::endl; }
};
int main() {
// 使用智能指针 std::unique_ptr 来管理 MyClass 的资源,当程序结束时,智能指针将自动释放内存
std::unique_ptr<MyClass> myClassPtr(new MyClass());
std::cout << "Resource is managed by smart pointer" << std::endl;
return 0;
}
并行
#include
#include
#include
#include
int sum(std::vector<int>::iterator begin, std::vector<int>::iterator end) {
int res = 0;
for (auto it = begin; it != end; ++it) {
res += *it;
}
return res;
}
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int n = v.size() / 2;
// 并发执行两个子任务
std::future<int> f1 = std::async(sum, v.begin(), v.begin() + n);
std::future<int> f2 = std::async(sum, v.begin() + n, v.end());
// 等待两个子任务完成,然后将结果相加
int res = f1.get() + f2.get();
std::cout << "The sum of the vector is: " << res << std::endl;
return 0;
}
并发
#include
#include
#include
#include
// 定义一个计算整数之和的函数
int sum(std::vector<int>::iterator begin, std::vector<int>::iterator end) {
int res = 0;
// 遍历迭代器范围内的所有整数,将它们的和累加到 res 变量中
for (auto it = begin; it != end; ++it) {
res += *it;
}
// 返回计算出的整数之和
return res;
}
int main() {
// 创建一个存储整数的向量
std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 计算向量的大小并分成两段
int n = v.size() / 2;
// 使用 std::async 并发执行两个子任务
std::future<int> f1 = std::async(sum, v.begin(), v.begin() + n);
std::future<int> f2 = std::async(sum, v.begin() + n, v.end());
// 等待两个子任务完成,然后将它们的结果相加
// 这里调用了 std::future 的 get() 函数以获取异步操作的结果
int res = f1.get() + f2.get();
// 打印向量的总和
std::cout << "The sum of the vector is: " << res << std::endl;
return 0;
}
并行
#include
#include
#include
#include
#include
int main() {
// 创建一个存储整数的向量,包含 100000000 个数
std::vector<int> v(100000000);
// 使用 iota 函数为向量中对整数赋值,从 1 开始连续递增
std::iota(v.begin(), v.end(), 1);
// 使用 std::for_each 并行遍历向量并对其中的每个元素求平方
// 注意:在 C++17 及以上的版本中需要在头文件中包含
std::for_each(std::execution::par, v.begin(), v.end(),
[](int& n) { n *= n; }
);
// 打印输出向量中的值,输出前 10 个值
std::cout << "The first 10 values of the vector are: ";
for (int i = 0; i < 10; ++i) {
std::cout << v[i] << ' ';
}
std::cout << std::endl;
return 0;
}
Memory safe C++ 是一种可免除手工向堆内存分配内存或者手动释放内存的 C++ 编写方式。它使用 RAII(资源获取即初始化)编程技巧来避免内存泄露和指针悬挂等内存安全问题,并提供强类型和数据结构来避免缓冲区溢出、堆栈溢出等问题。
#include
#include
int main()
{
// 创建指向整型变量的智能指针
std::shared_ptr<int> ptr(new int(42));
// 智能指针会在作用域结束时自动释放内存
std::cout << *ptr << std::endl;
return 0;
}
创建一个指向 int 所在的内存地址的 shared_ptr,并在它的作用域结束时自动回收它。
#include
class MyArray {
public:
MyArray(size_t size)
: m_array(new int[size]), m_size(size) // 构造函数,初始化类成员变量
{}
~MyArray() // 析构函数,释放掉堆中的内存
{
delete[] m_array;
}
int& operator[](size_t index) // 重载 [] 操作符,返回下标对应的元素的引用
{
return m_array[index];
}
private:
int* m_array; // 指向堆中数组的指针
size_t m_size; // 数组的大小
};
int main() { // 使用 RAII 类来创建一个堆数组,避免手动管理内存
MyArray arr(10);
arr[0] = 10;
arr[1] = 20;
std::cout << arr[0] << std::endl; // 输出数组元素
std::cout << arr[1] << std::endl;
return 0;
}
创建了一个 MyArray 类来管理一个动态分配的 int 数组。构造函数使用 new 创建一个数组,并在类的析构函数中释放堆内存。由于实现了 RAII,所以我们不需要手动管理内存,它会自动回收。
#include
#include
#include
class Resource {
public:
Resource() {
m_resource = new int[1024]; // 构造函数,分配资源
}
~Resource() {
delete[] m_resource; // 析构函数,释放资源
}
void useResource() // 使用资源的函数,抛出异常
{
std::cout << "Using resource...\n";
throw std::runtime_error("Exception in useResource()");
}
private:
int* m_resource;
};
int main() {
std::unique_ptr<Resource> ptr1(new Resource); // 使用 unique_ptr 管理资源
try {
ptr1->useResource(); // 使用资源,运行时抛出异常
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl; // 捕获异常并输出
}
return 0; // 程序结束,ptr1 所管理的资源会被自动释放
}
在 C++ 中,内存是由一组字节组成的线性地址空间,每个字节都有一个唯一的地址。数据在内存中被表示为对象,每个对象占用着内存中的一些字节。这些对象可以是内置数据类型(例如整数或浮点数),也可以是用户定义的类型,如结构体或类。
在 C++ 中,内存按顺序组织,每个对象在内存中占据一定的连续字节,以便通过内存地址来访问和操作对象。例如,可以通过指针来访问存储在内存中相邻位置的对象,也可以使用指针算术运算来跨越一部分内存中的对象,以访问另一个对象。
#include
int main() {
int a = 42;
double b = 3.14;
// 获取变量 a 的内存地址,并打印该地址
std::cout << "Address of a: " << &a << std::endl;
int* ptr = &a;
// 获取指针 ptr 指向的内存地址,并打印该地址
std::cout << "Address of ptr: " << ptr << std::endl;
// 使用指针来访问变量 a 的值,并打印该值
std::cout << "Value of a: " << *ptr << std::endl;
// 增加指针以访问变量 b 的地址和值
ptr = reinterpret_cast<int*>(&b);
std::cout << "Address of ptr: " << ptr << std::endl;
std::cout << "Value of b: " << *ptr << std::endl;
return 0;
}
如果您的代码中没有使用这些抽象语言特性,那么您不会为此付出任何额外的成本。 这句话的意思是,C++实现了一种高效的对象抽象机制,只有真正需要使用这些抽象特性的时候,才会引入相应的开销,否则不会增加额外负担。
使用C++开发的应用程序通常比手动编写的代码更为高效。C++可以自动优化代码,并利用硬件特性,从而获得更快的执行速度。因此,即使使用了高级功能,C++代码仍然可能比手动编写代码更快。
Point,complex,date和tuple等对象都是轻量级的对象类型
constexpr int fibonacci(int n)
{
return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
int main()
{
constexpr int size = 10;
int array[size];
for (int i = 0; i < size; i++) {
array[i] = fibonacci(i);
}
// Use array here
return 0;
}
“资源”指的是一些计算机系统中有限的、需要被程序显式或隐式占用的资源,例如内存、文件句柄、网络连接、锁等等。这些资源需要在程序中被“获取”或“占用”,并在使用完成后“释放”或“归还”。
#include
#include
#include
using namespace std;
template <typename T>
class Vector {
public:
Vector(initializer_list<T> lst)
: sz{lst.size()}, elem{new T[sz]} {
copy(lst.begin(), lst.end(), elem);
}
~Vector() {
delete[] elem;
}
// ...
T& operator[](int i) {
assert(i >= 0 && i < sz);
return elem[i];
}
int size() const {
return sz;
}
private:
T* elem;
size_t sz;
};
void fct() {
Vector<double> v{1.0, 1.618, 3.14, 2.99e8};
assert(v.size() == 4); // 确认 v 的大小
assert(v[0] == 1.0); // 确认 v 的第一个元素等于 1.0
v[0] = 2.0; // 修改 v 的第一个元素
assert(v[0] == 2.0); // 确认 v 的第一个元素是否成功修改
}
int main() {
fct(); // 执行测试函数
return 0;
}
在局部作用域中使用指针的操作往往不具备异常安全性
void f(int n, int x)
{
Gadget* p = new Gadget{n}; // Java 程序员!
// …
if (x < 100)
throw std::run_time_error{"出现了奇怪的问题!"}; // 内存泄漏
if (x < 200)
return; // 内存泄漏
// …
delete p; // 我需要 Java 的垃圾回收器!
}
内存泄露
禁止使用“裸的 new”,即不要使用不带管理的指针申请内存,也不要使用“裸指针”,即没有内存安全保障的指针。
std::shared_ptr会在最后一个指向它的shared_ptr被销毁时隐式地释放它的对象
void f(int n, int x)
{
auto p = make_shared<Gadget>(n); // 使用 shared_ptr 管理指针
// 返回一个 shared_ptr
// …
if (x < 100)
throw std::runtime_error{"出现了奇怪的问题!"}; // 无内存泄漏
if (x < 200)
return; // 无内存泄漏
// …
}
shared_ptr 提供了一种类似垃圾回收的机制,可以自动管理内存资源的释放,避免了手动释放内存的工作
如果可以的话,尽量避免使用指针,直接使用一个有作用域的变量,这通常是句柄(handle)
void f(int n, int x)
{
Gadget g {n};
// …
if (x < 100)
throw std::runtime_error{"出现了奇怪的问题!"}; // 无内存泄漏
if (x < 200)
return; // 无内存泄漏
// …
}
控制完整的对象生命周期
-创造、复制、移动、销毁
#include
#include
class Gadget {
public:
// 构造函数
Gadget(int n) {
std::cout << "Gadget constructor called with " << n << std::endl;
}
// 移动构造函数
Gadget(Gadget&& other) noexcept {
std::cout << "Gadget move constructor called" << std::endl;
}
// 删除复制构造函数和赋值运算符
Gadget(const Gadget& other) = delete;
Gadget& operator=(const Gadget& other) = delete;
};
Gadget f(int n, int x) {
Gadget g{n}; // g 可能很大
// g 可以包含非可复制对象
// ...
return g; // 无泄漏,无复制
// 无指针
// 无显式内存管理
}
int main() {
auto gg = f(1, 2); // 从函数 f 中移动出 Gadget
// gg 是从函数 f 中移动出的 Gadget,无需复制内存,也无内存泄漏和显式内存管理。
return 0;
}
作用域,由析构函数保护
使资源释放隐式和有保证(RAII)
所有c++标准库容器都会管理它们的element
– vector
– list, forward_list (singly-linked list), …
– map, unordered_map (hash table),…
– set, multi_set, …
– strin
一些c++标准库类管理非内存资源
– thread, lock_guard, …
– istream, fstream, …
– unique_ptr, shared_ptr
容器可以容纳非内存资源
有时,指针语义是必不可少的——您需要指针/引用来实现运行时多态性
#include
#include
#include
class Point {
public:
int x;
int y;
};
class Shape {
public:
// 纯虚函数,需要在子类中实现
virtual void draw() const = 0;
};
class Circle : public Shape {
public:
Circle(Point p, int r)
: center{p}, radius{r} {}
void draw() const override {
// 输出圆形的中心点坐标和半径
std::cout << "Circle drawn with center (" << center.x << "," << center.y << ") "
<< "and radius " << radius << std::endl;
}
private:
Point center; // 中心点坐标
int radius; // 半径
};
class Triangle : public Shape {
public:
Triangle(Point p1, Point p2, Point p3)
: pt1{p1}, pt2{p2}, pt3{p3} {}
void draw() const override {
// 输出三角形的三个顶点坐标
std::cout << "Triangle drawn with points (" << pt1.x << "," << pt1.y << "),"
<< " (" << pt2.x << "," << pt2.y << "), and (" << pt3.x << "," << pt3.y << ")" << std::endl;
}
private:
Point pt1; // 顶点 1
Point pt2; // 顶点 2
Point pt3; // 顶点 3
};
// range auto 是 C++20 的新特性,用于支持基于范围的 for 循环
// 增加了一个要求,即容器中的每个元素的类型必须为 Shape 的子类
// T& 表示传入的容器类型的引用
template <typename T>
struct remove_sptr {
using type = typename T::element_type;
};
template <typename T>
using remove_sptr_t = typename remove_sptr<T>::type;
template <typename T>
using Value_type = remove_sptr_t<typename T::value_type>;
template <typename T>
concept derived_from_Shape = std::derived_from<Value_type<T>, Shape>;
// 该函数用于遍历容器中的所有元素,并且调用每个元素的 draw() 函数
// 参数 T& 表示接收一个容器的引用,容器中的每个元素的类型都需要为 Shape 的子类
template <derived_from_Shape T>
// template
void draw_all(T& s) {
// 基于范围的 for 循环,遍历容器 s
for (auto& x : s) {
x->draw(); // 调用每个元素的 draw() 函数
}
}
// 用户代码
void user(Point p2, Point p3) {
// 定义一个 shared_ptr 的 Shape 列表
std::vector<std::shared_ptr<Shape>> lst = {
// 向列表中添加一个 Circle 对象,中心点坐标为 (0, 0),半径为 42
std::make_shared<Circle>(Point{0, 0}, 42),
// 向列表中添加一个 Triangle 对象,顶点坐标分别为 (20, 200),p2 和 p3
std::make_shared<Triangle>(Point{20, 200}, p2, p3),
// …
};
// …
// 调用 draw_all() 函数,遍历 lst 中的所有元素,并调用每个元素对象的 draw() 函数
draw_all(lst);
}
int main() {
// 调用 user() 函数
user(Point{30, 30}, Point{70, 50});
return 0;
}
使用“智能”指针来避免内存泄漏
c++不仅仅是一门面向对象的语言
C++支持许多编程范式,如面向过程编程和泛型编程,C++支持许多编程范式,如面向过程编程和泛型编程
能够与计算机硬件进行直接交互。
函数与数据的分离
#include
#include
#include
using namespace std;
template <typename C, typename V>
vector<typename C::value_type*> find_all(C& c, V v) {
vector<typename C::value_type*> res; // 存放符合条件的值类型的指针的 vector
for (auto& x : c) { // 遍历容器 C,auto& 自动推导 x 的类型为 C 的值类型的引用
if (x == v) {
res.push_back(&x); // 将符合条件的值类型的指针加入到 res 中
}
}
return res; // 返回符合条件的值类型的指针的 vector
}
int main() {
string m{"Mary had a little lamb"}; // 定义一个字符串
for (const auto p : find_all(m, 'a')) { // 遍历字符串 m 中所有值为 'a' 的字符的指针
if (*p != 'a') { // 如果值类型不是 'a',输出错误信息
cerr << "string bug!\n"; // 输出错误信息到标准错误流中
}
}
return 0;
}
int i;
for (i=0; i<max; ++i) f(v[i]); // C-style loop
for (int i=0; i<max; ++i) f(v[i]); // C++98: C-style loop
for (int& x : v) f(x); // C++11: range-for
for_each(begin(v),end(v),f); // C++11: algorithms
for_each(par_unseq,v,f); // C++20: parallel algorithm and range
template<typename T>
void printData(const T& data) {
if constexpr (std::is_integral_v<T>) { // 使用了只在编译期执行的 if constexpr 语句,可以根据 T 的类型在编译期选择执行不同的代码块
std::cout << "Integer data: " << data << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Floating point data: " << data << std::endl;
} else {
std::cout << "Unknown data type!" << std::endl;
}
}
在编译期间使用类型信息,可以让程序的表现更加丰富和高效。根据数据类型的不同,我们可以选择执行不同的代码块,从而减少了运行时条件分支,并提高了程序的性能。
const int MAX_ELEMENTS = 100; // 定义不可变的数组长度
void processArray(int arr[MAX_ELEMENTS]) { // 使用定义好的常量
// ...
}
由于 MAX_ELEMENTS 是一个常量,所以它的值在程序执行期间不会发生变化。在并发环境中,我们可以使用这个常量来定义数组长度,而无需担心它在多个线程之间出现竞争问题。
template<typename T>
T divide(T a, T b) {
static_assert(std::is_floating_point_v<T>, "Only floating point division is allowed!"); // 使用编译期的断言 static_assert ,在编译期就捕获错误情况
if (b == 0) {
throw std::logic_error("Division by zero!");
}
return a / b;
}
在编写 divide() 函数时,我们使用了静态断言 static_assert,以确保该函数只适用于浮点数类型。如果使用其他类型,该函数将在编译时产生一个错误。此外,在运行时,如果发现除数为零,该函数将抛出一个异常。这种方式避免了在运行时进行错误处理,因为我们可以在编译程序时捕获大多数错误情况。
const double PI = 3.141592653589793; // 定义常量
double circleArea(double radius) {
return PI * radius * radius; // 使用定义的常量进行计算
}
为避免手算值出现的问题,我们可以定义一个不变的常量 PI,以保证更高的精度和可读性。
适用于所有满足抽象要求的类型的代码
例如,是前向迭代器、整数、可比较排序的可规范化类型。
Requirements defined as concepts(将要求定义为概念)
#include
#include
#include
// 定义 printable 概念,要求类型 T 必须支持在 std::cout 中输出 template
template <typename T>
concept printable = requires(const T& t) {
{ std::cout << t } -> std::same_as<std::ostream&>;
};
// 定义 integral_container 概念,要求类型 T 必须是容器类型,且元素类型为 int
template <typename T>
concept integral_container = requires(T container) {
requires std::same_as<typename T::value_type, int>;
// requires std::same_as;
{ container.begin() } -> std::same_as<typename T::iterator>;
{ container.end() } -> std::same_as<typename T::iterator>;
{ container.cbegin() } -> std::same_as<typename T::const_iterator>;
{ container.cend() } -> std::same_as<typename T::const_iterator>;
};
template <printable T>
void print(const T& value) {
std::cout << value << '\n';
}
// template
template <integral_container T>
int sum(const T& container) {
int sum = 0;
for (auto it = container.cbegin(); it != container.cend(); ++it) {
sum += *it;
}
return sum;
}
int main() {
int x = 5;
print(x);
std::vector<int> v{1, 2, 3, 4, 5};
std::cout << sum(v) << '\n';
// std::vector f{1.0, 2.0, 3.0}; // 在编译时会导致requirement失败,从而编译失败
return 0;
}
#include
#include
auto my_task = [](std::stop_token tok) {
while (!tok.stop_requested()) { // 在未被请求停止前一直执行任务
// ... do work ...
}
};
void user() {
std::jthread t1{my_task}; // 使用 jthread 创建线程并传入 my_task 函数,stop_token 由 jthread 隐式传递
std::jthread t2{my_task};
bool t1_no_longer_needed = true; // 声明 t1_no_longer_needed 变量,表示 t1 不再需要执行
// ...
if (t1_no_longer_needed) {
t1.request_stop(); // 请求 t1 停止
}
// ...
}
int main() {
user(); // 启动 user 函数,其中包含了创建线程、请求线程停止的操作
return 0;
}
#include
#include
#include
#include
class Singleton {
public:
static Singleton& getInstance() {
Singleton* instance = m_instance.load(std::memory_order_acquire); // 获取实例指针
if (!instance) { // 如果指针为空,则需要创建实例
std::lock_guard<std::mutex> lock(m_mutex); // 锁定实例对象,保证只会有一个线程创建实例
instance = m_instance.load(std::memory_order_relaxed); // 再次获取实例指针,防止其他线程在等待锁时已经创建了实例
if (!instance) { // 如果依然为空,则需要在当前线程中创建实例
instance = new Singleton(); // 创建实例
m_instance.store(instance, std::memory_order_release); // 储存实例指针
}
}
return *instance; // 返回实例引用
}
void print() { // 测试输出函数
std::cout << "Hello, Singleton!" << std::endl;
}
private:
static std::atomic<Singleton*> m_instance; // 原子指针,确保在多线程环境下读写安全
static std::mutex m_mutex; // 互斥锁,确保在多线程环境下只有一个线程能够创建实例
Singleton() = default; // 构造函数,默认使用编译器生成的构造函数
~Singleton() = default; // 析构函数,默认使用编译器生成的析构函数
Singleton(const Singleton&) = delete; // 禁止拷贝构造函数
Singleton& operator=(const Singleton&) = delete; // 禁止赋值操作
};
std::atomic<Singleton*> Singleton::m_instance(nullptr); // 全局变量初始化
std::mutex Singleton::m_mutex; // 全局变量初始化
void std_thread_test() { // 使用std::thread进行测试的函数
std::thread t1([]() { // 创建线程1
Singleton& s = Singleton::getInstance(); // 获取实例引用
std::cout << "Thread 1: ";
s.print(); // 输出信息
});
std::thread t2([]() { // 创建线程2
Singleton& s = Singleton::getInstance(); // 获取实例引用
std::cout << "Thread 2: ";
s.print(); // 输出信息
});
t1.join(); // 等待线程1执行完毕
t2.join(); // 等待线程2执行完毕
}
int main() {
std_thread_test(); // 使用std::thread进行测试
// jthread测试用例
// auto func = [](){ // 创建测试函数
// Singleton& s = Singleton::getInstance(); // 获取实例引用
// std::cout << "Thread ID: " << std::this_thread::get_id() << ", Singleton address: " << &s << std::endl; // 输出线程ID与实例地址
// };
// std::jthread t1(func); // 创建线程1
// std::jthread t2(func); // 创建线程2
return 0; // 返回0,退出程序
}
std::mutex:基本的互斥锁;
std::recursive_mutex:支持递归锁的互斥锁;
std::timed_mutex:支持超时的互斥锁;
std::recursive_timed_mutex:支持递归锁和超时的互斥锁;
std::shared_mutex:支持读写锁的互斥锁;
std::shared_timed_mutex:支持读写锁和超时的互斥锁。
#include
#include
#include
int g_count = 0; // 全局计数器
std::mutex g_mutex; // 全局互斥锁
void increment() {
std::lock_guard<std::mutex> lock(g_mutex); // 创建一个lock_guard对象来管理互斥锁
++g_count; // 修改计数器
}
int main() {
constexpr int NUM_THREADS = 10;
std::thread threads[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; ++i) {
threads[i] = std::thread(increment); // 创建10个线程,每个线程调用increment函数
}
for (int i = 0; i < NUM_THREADS; ++i) {
threads[i].join(); // 等待所有线程结束
}
std::cout << "The result is " << g_count << std::endl; // 输出计数器的值
return 0;
}
#include
#include
#include
#include
using namespace std;
// 定义异步任务类型 async_task
struct async_task {
// 定义异步任务的 promise_type
struct promise_type {
// 获取异步任务对象
auto get_return_object() { return async_task{handle_type::from_promise(*this)}; }
// 初始化异步任务状态,暂不挂起异步任务
auto initial_suspend() { return suspend_never{}; }
// 结束异步任务的挂起,完全销毁异步任务
auto final_suspend() noexcept { return suspend_always{}; }
// 异步任务执行出现未处理的异常,强制退出程序
void unhandled_exception() { std::terminate(); }
// 异步任务无返回值,不做任何操作
void return_void() noexcept {}
};
// 声明异步任务的协程句柄 handle_type
using handle_type = std::coroutine_handle<promise_type>;
// 异步任务的协程句柄
handle_type coro_;
// 异步任务对象的构造函数
async_task(handle_type h)
: coro_(h) {}
// 移动构造函数
async_task(async_task&& other) noexcept
: coro_(other.coro_) {
other.coro_ = nullptr;
}
// 异步任务是否处于就绪状态,恒为 false,不能使用 co_await 挂起
bool await_ready() const noexcept { return false; }
// 当前协程挂起,使用新线程执行异步任务,并在异步任务完成后恢复协程的执行
void await_suspend(std::coroutine_handle<promise_type> h) {
std::thread([h]() { h.resume(); }).detach();
}
// 异步任务不返回结果,恒为空操作,不能使用 co_await 恢复协程的执行
void await_resume() const noexcept {}
};
// 定义异步任务函数 async_function
async_task async_function() {
// 使用 co_await 挂起异步任务,这里使用 suspend_always,也可以使用其它的协程挂起器
co_await std::suspend_always{};
// 异步任务执行的具体操作
cout << "async function" << endl;
}
int main() {
// 异步调用 async_function,返回异步任务对象
auto task = async_function();
// 等待异步任务完成
task.await_resume();
// 主函数执行的操作
cout << "main function" << endl;
return 0;
}
在这个例子中,我们定义了一个异步任务函数async_function(),它使用co_await关键字挂起异步任务,并在异步任务中执行cout操作。
在主函数中,我们调用了异步任务函数async_function(),并使用await_resume()函数等待异步任务完成。同时,主函数也执行了cout
操作。
#include
#include
#include
using namespace std;
// 定义斐波那契数列生成器
struct fibonacci_generator {
// 定义斐波那契数列生成器的 promise_type
struct promise_type {
// 当前斐波那契数列元素和下一个元素
int current_ = 0, next_ = 1;
// 获取斐波那契数列生成器对象
fibonacci_generator get_return_object() {
return fibonacci_generator{handle_type::from_promise(*this)};
}
// 初始化斐波那契数列生成器状态,立即挂起异步任务
auto initial_suspend() { return suspend_always{}; }
// 结束斐波那契数列生成的挂起,完全销毁斐波那契数列生成器
auto final_suspend() noexcept(true) { return suspend_always{}; }
// 斐波那契数列生成无返回值,不做任何操作
void return_void() {}
// 异步生成斐波那契数列时,若出现未处理的异常,强制退出程序
void unhandled_exception() { std::terminate(); }
// 将生成的元素值作为参数,保存并暂时挂起异步任务
auto yield_value(int value) {
current_ = value;
return suspend_always{};
}
};
// 声明斐波那契数列生成器的协程句柄 handle_type
using handle_type = std::coroutine_handle<promise_type>;
// 斐波那契数列生成器的协程句柄
handle_type coro_;
// 斐波那契数列生成器对象的构造函数
explicit fibonacci_generator(handle_type h)
: coro_(h) {}
// 不使用复制构造函数
fibonacci_generator(const fibonacci_generator&) = delete;
// 移动构造函数
fibonacci_generator(fibonacci_generator&& other) noexcept
: coro_(other.coro_) {
other.coro_ = nullptr;
}
// 析构函数,若存在协程句柄,销毁它
~fibonacci_generator() {
if (coro_)
coro_.destroy();
}
// 迭代到下一个斐波那契数列元素
void move_next() { return coro_.resume(); }
// 获取当前斐波那契数列元素的值
int current_value() { return coro_.promise().current_; }
};
// 异步生成斐波那契数列的协程函数
fibonacci_generator fibonacci_sequence() {
int a = 0, b = 1;
while (true) {
co_yield a; // 生成斐波那契数列的元素
// 计算下一次要生成的斐波那契数列元素
tie(a, b) = make_pair(b, a + b);
}
}
int main() {
auto gen = fibonacci_sequence(); // 创建斐波那契数列生成器
for (int i = 0; i < 20; ++i) {
gen.move_next(); // 生成下一个斐波那契数列元素
cout << gen.current_value() << endl; // 输出当前元素的值
}
return 0;
}
在这个例子中,我们使用协程实现了一个斐波那契数列生成器,利用co_yield关键字返回生成器的下一个元素。在主函数中,我们调用生成器
的move_next()函数获取生成器的下一个元素,直到生成器返回20个元素。
#include
#include
using namespace std;
// 定义协程封装类 task
struct task {
// 定义 Promise 类型,用于返回协程对象的初始化器
struct promise_type {
// 返回协程对象的初始化器
auto get_return_object() { return std::coroutine_handle<promise_type>::from_promise(*this); }
// 协程启动时的暂停器,返回值为 std::suspend_always 类型
auto initial_suspend() { return suspend_always{}; }
// 协程结束时的暂停器,返回值为 std::suspend_always 类型
auto final_suspend() noexcept(true) { return suspend_always{}; }
// 协程结果为空,即返回值为 void
void return_void() {}
// 处理异常
void unhandled_exception() {}
};
// 定义协程句柄类型为 std::coroutine_handle
using handle_type = std::coroutine_handle<promise_type>;
handle_type coro_;
// 构造函数,将协程句柄作为参数,将其保存到成员变量中
task(handle_type h)
: coro_(h) {}
// 析构函数,如果协程句柄可用,销毁它
~task() {
if (coro_)
coro_.destroy();
}
// 恢复协程的执行并切换到它的上下文
void resume() { coro_.resume(); }
};
// 定义一个协程函数 foo,用于演示协程的使用
task foo(int i) {
cout << "start coroutine " << i << endl;
// 调用 co_await 操作符,等待一个 std::suspend_always 类型的暂停器
co_await std::suspend_always{};
cout << "resume coroutine " << i << endl;
}
int main() {
// 创建两个 task 类型的对象 t1 和 t2,分别调用 foo 函数
task t1 = foo(1);
task t2 = foo(2);
// 切换协程 t1 的状态
t1.resume();
// 切换协程 t2 的状态
t2.resume();
// 再次切换协程 t1 的状态
t1.resume();
// 再次切换协程 t2 的状态
t2.resume();
return 0;
}
在这个例子中,我们使用协程实现了两个任务函数foo(),每个任务函数输出开始和恢复的信息,并使用std::suspend_always{}暂停任
务函数。在主函数中,我们创建了两个任务对象t1和t2,通过调用对象的resume()函数交替执行两个任务。在任务函数中使用std::co_awa
it关键字暂停任务,等待其他任务执行完成。
#include
#include
#include
int main() {
std::variant<int, double, std::string> v;
v = 42;
std::cout << std::get<int>(v) << std::endl;
v = 3.14;
std::cout << std::get<double>(v) << std::endl;
v = "Hello, world!";
std::cout << std::get<std::string>(v) << std::endl;
// 这里会抛出异常,因为v存储的是std::string类型,而不是int类型
std::cout << std::get<int>(v) << std::endl;
return 0;
}
#include
#include
int main() {
int arr[] = {1, 2, 3, 4, 5};
std::span<int> s(arr, 5); // 创建一个 span 对象,表示整个数组
std::cout << "Size of span: " << s.size() << std::endl; // 输出 5
std::cout << "First element: " << s[0] << std::endl; // 输出 1
std::cout << "Last element: " << s[s.size() - 1] << std::endl; // 输出 5
for (int& i : s) {
i *= 2; // 将数组中的每个元素乘以 2
}
for (int i : arr) {
std::cout << i << ' '; // 输出修改后的数组元素
}
return 0;
}
不是一个可接受的解决方案 - 特别是
“smart pointers”也不是一个完整的解决方案