c++ 学习笔记

Overview

1. 泛型编程和编译器计算

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;
}

2. 资源安全


#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;
}

3. 并发和并行(Concurrency and parallelism)

并行


#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(资源获取即初始化)编程技巧来避免内存泄露和指针悬挂等内存安全问题,并提供强类型和数据结构来避免缓冲区溢出、堆栈溢出等问题。

1 智能指针(例如 std::shared_ptr 或 std::unique_ptr)


#include 
#include 

int main()
{
    // 创建指向整型变量的智能指针
    std::shared_ptr<int> ptr(new int(42));

    // 智能指针会在作用域结束时自动释放内存
    std::cout << *ptr << std::endl;

    return 0;
}

创建一个指向 int 所在的内存地址的 shared_ptr,并在它的作用域结束时自动回收它。

2. RAII 类,使用堆分配的内存


#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,所以我们不需要手动管理内存,它会自动回收。

3. 避免内存泄露的异常安全


#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++机器模式

原语操作映射到机器指令- +,%,->, [ ], (),

内存是一组顺序排列的对象集合。

在 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++的一个关键 这是对硬件的简单抽象

零开销的抽象

  1. 如果您的代码中没有使用这些抽象语言特性,那么您不会为此付出任何额外的成本。 这句话的意思是,C++实现了一种高效的对象抽象机制,只有真正需要使用这些抽象特性的时候,才会引入相应的开销,否则不会增加额外负担。

  2. 使用C++开发的应用程序通常比手动编写的代码更为高效。C++可以自动优化代码,并利用硬件特性,从而获得更快的执行速度。因此,即使使用了高级功能,C++代码仍然可能比手动编写代码更快。

  3. Point,complex,date和tuple等对象都是轻量级的对象类型

  • 这些对象都是小而轻,通常不需要分配在 heap 的空间,因为它们可以直接存储在 stack 上。
  • 这些对象所包含的函数通常都是 inline 实现的,这意味着在调用这些函数时,并不会引入额外的函数调用开销。C++编译器会将函数定义的代码直接嵌入到程序的执行流中,而不是在调用该函数时从外部执行。
  • 因为这些对象是小而轻,它们可以直接存储在函数或类的栈中,而不需要在堆上动态分配内存。
  • 这些对象的成员函数往往会被定义成inline函数,这样编译器可以在编译期间将函数体代码嵌入到函数调用点处,从而减少了函数调用时的开销,提高了程序的执行效率。
  • 在编译时,C++编译器可以计算常量表达式等,这样在程序运行时就不需要再进行计算,从而提高程序的效率。
    • constexpr是C++11引入的关键字,它可以用来声明一个函数或变量是在编译期间求值的,从而允许在编译期计算表达式的值。
    • template也可以实现在编译期间求值,因为它可以展开为一些常量表达式。

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;
}

资源管理

“资源”指的是一些计算机系统中有限的、需要被程序显式或隐式占用的资源,例如内存、文件句柄、网络连接、锁等等。这些资源需要在程序中被“获取”或“占用”,并在使用完成后“释放”或“归还”。

  • “显式”和“隐式”
  • 资源泄漏最终导致计算机故障
  • 过多的资源占用会使计算机运行速度变慢
  • Examples内存、锁、文件句柄、网络套接字、线程句柄
  • 应该避免手动管理资源,以避免泄漏和占用过多资源

类、构造函数和析构函数


#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++不仅仅是面向对象编程

  1. c++不仅仅是一门面向对象的语言
    C++支持许多编程范式,如面向过程编程和泛型编程,C++支持许多编程范式,如面向过程编程和泛型编程

  2. 能够与计算机硬件进行直接交互。

  3. 函数与数据的分离


#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

Type-rich programming at compile tim


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;
    }
}

在编译期间使用类型信息,可以让程序的表现更加丰富和高效。根据数据类型的不同,我们可以选择执行不同的代码块,从而减少了运行时条件分支,并提高了程序的性能。

  1. 常量不能有竞态条件

const int MAX_ELEMENTS = 100; // 定义不可变的数组长度

void processArray(int arr[MAX_ELEMENTS]) { // 使用定义好的常量
    // ...
}

由于 MAX_ELEMENTS 是一个常量,所以它的值在程序执行期间不会发生变化。在并发环境中,我们可以使用这个常量来定义数组长度,而无需担心它在多个线程之间出现竞争问题。

  1. 不需要运行时错误处理

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,以确保该函数只适用于浮点数类型。如果使用其他类型,该函数将在编译时产生一个错误。此外,在运行时,如果发现除数为零,该函数将抛出一个异常。这种方式避免了在运行时进行错误处理,因为我们可以在编译程序时捕获大多数错误情况。

  1. 手工计算值容易出错

const double PI = 3.141592653589793; // 定义常量

double circleArea(double radius) {
    return PI * radius * radius; // 使用定义的常量进行计算
}

为避免手算值出现的问题,我们可以定义一个不变的常量 PI,以保证更高的精度和可读性。

泛型编程

  1. 适用于所有满足抽象要求的类型的代码
    例如,是前向迭代器、整数、可比较排序的可规范化类型。

  2. 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;
}

jthread


#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;
}

Double-locked initialization


#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;
}

协程

  1. 异步操作

#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
操作。

  1. 使用协程实现生成器

#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个元素。

  1. 使用协程实现状态机



  1. 使用协程实现协作式多任务

#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关键字暂停任务,等待其他任务执行完成。

破坏类型系统的其他方面

  1. unions:使用 std::variant
  2. 强制转换:不使用

#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;
}

  1. nullptr解引用:使用not_null
  2. Range错误:使用算法和span

#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”也不是一个完整的解决方案

  • 我们需要全面的准则
  • 静态执行。

c++11

  • 并发支持
  • 内存模型
  • 原子操作和无锁编程
  • 线程、互斥量、条件变量、future等
  • 移动语义
  • 广义常量表达式求值(包括constexpr)
  • Lambda 表达式
  • auto 关键字
  • Range-for 循环
  • override 和 final 关键字
  • =delete 和 =default 关键字
  • 统一初始化({} 初始化方式)
  • 自定义字面量
  • nullptr 关键字
  • static_assert 关键字
  • 可变参数模板
  • 原始字符串字面量(R"(…)")
  • long long 关键字
  • 成员初始化列表
  • shared_ptr and unique_pt
  • Hash tables

C++14: “Completes C++11

  • 函数返回类型推断,例如 auto f(T& x) { return g(x); }
  • 放宽 constexpr 的限制,例如在 constexpr 函数中支持 for 循环
  • 变量模板
  • 二进制字面量,例如 0b0101000011111010
  • 数字分隔符,例如 0b0101’0000’1111’1010
  • 泛型 lambda 表达式,例如 [](auto x) { return x+x; }
  • Lambda 捕获表达式
  • [[deprecated]] 注解
  • 共享互斥量
  • 时间的用户定义字面量
  • 基于类型的元组访问,例如 get(t)
  • … (省略号代表可能还有其他 C++14 特性未列出)

C++17: “a little bit for everyone”

  • 结构化绑定,例如 auto [re,im] = complex_algo(z);
  • 模板参数推断,例如 pair p {2, "Hello!"s};
  • 更可靠的求值顺序,例如 m[0] = m.size();
  • 编译时 if,例如 if constexpr(f(x)) …
  • 推断模板参数值的类型,例如 template …
  • 带初始化的 if 和 switch,例如 if (X x = f(y); x)…
  • 可以为超对齐数据进行动态内存分配
  • 内联变量(Yuck!)
  • [[fallthrough]]、[[nodiscard]]、[[maybe unused]] 等注解
  • 用于参数包的折叠表达式,例如 auto sum = (args + …);
  • 文件系统库
  • 并行库,例如 sort(par_unseq,v.begin(),v.end())
  • 特殊的数学函数库,例如 riemann_zeta()
  • variant、optional、any、string_view 等容器或类型总称
  • … (省略号代表可能还有其他 C++17 特性未列出)

C++20: A “major release”

  • Concepts
  • Modules
  • Coroutines
  • 更好的并行算法
  • 加入线程和停止tokens
  • Ranges(使用Concepts)
  • Span(对连续序列进行安全、高效的访问)
  • Format(类型安全的printf风格的格式化)
  • Source_location,数学常数,位运算

你可能感兴趣的:(c++,学习,算法)