[C/C++] -- C++11相关内容

一:声明

  • auto:

auto 是 C++11 引入的一个关键字,用于自动推断变量的类型。通过使用 auto,编译器可以根据变量的初始化表达式推断其类型,从而减少代码中的重复冗长的类型声明。

简化模板声明:

for(auto p = vec.begin();p!=vec.end();p++)
  • decltype

decltype 是 C++11 引入的一个关键字,用于获取表达式的类型,而不进行实际的表达式求值。它的作用是让编译器在编译时推断表达式的类型,并将其作为编译器生成的类型声明的一部分。

获取变量的类型:

int x = 5;
decltype(x) y; // y 的类型为 int,与 x 的类型相同

获取表达式的类型:

int a = 10;
double b = 20.5;
decltype(a + b) c; // c 的类型为 double,因为 a + b 的类型为 double

结合 autodecltype

int x = 5;
auto y = x; // y 的类型为 int,auto 推断为 int
decltype(auto) z = x; // z 的类型为 int&,decltype(auto) 保留了 x 的引用类型

获取函数返回值类型:

int foo();
decltype(foo()) result; // result 的类型为 foo() 函数返回值的类型
  • auto

    • 简化模板代码,减少模板参数的复杂性。
    • 适用于需要简化类型声明,提高代码可读性的场合。
  • decltype

    • 在需要编写泛型代码时,用于捕获表达式的确切类型。
    • 与模板结合,用于处理复杂的类型推导,例如迭代器或容器中的类型。
    • 在需要保留变量引用类型的场合。
int arr[5] = {1, 2, 3, 4, 5};
decltype(arr) arr_copy; // arr_copy 的类型是 int[5]
auto arr_ref = arr; // arr_ref 的类型是 int*

当我们声明 int arr[5] = {1, 2, 3, 4, 5}; 时,我们创建了一个名为 arr 的数组,其中包含了5个整数元素。

  1. decltype(arr) arr_copy

    这行代码中,decltype(arr) 将返回 arr 的类型,即 int[5],表示一个包含5个整数元素的数组。因此,arr_copy 的类型也是 int[5],它是一个未初始化的包含5个整数元素的数组。
  2. auto arr_ref = arr

    这里使用 auto 推导 arr_ref 的类型。因为 arr 是一个数组,当数组名被用作表达式时,它会自动退化为指向数组首元素的指针(即 int* 类型)。因此,arr_ref 的类型被推导为 int*,表示一个指向整数的指针,指向数组 arr 的首元素。

所以,arr_copy 是一个未初始化的包含5个整数元素的数组,而 arr_ref 是一个指向数组 arr 的首元素的指针。

  • 返回类型后置

在C++11之前,返回类型通常在函数签名的前面指定,例如:

int add(int a, int b) {
    return a + b;
}

然而,随着C++11的引入,一种新的语法允许在函数签名之后指定返回类型,这被称为返回类型后置(Trailing Return Type)。这种语法对于需要从参数中推导返回类型的情况特别有用,并且在与 decltype 结合时非常方便。

#include <iostream>

// 使用返回类型后置来定义一个函数,返回两个输入参数的和。
auto add(int a, int b) -> int {
    return a + b;
}

// 使用返回类型后置和 decltype 来推导返回类型。
template <typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
    return a * b;
}

int main() {
    std::cout << "Add: " << add(3, 4) << std::endl; // 返回类型是 int
    std::cout << "Multiply: " << multiply(3, 4.5) << std::endl; // 返回类型由 decltype 推导
    return 0;
}
  • 模板别名:using=

在C++11中,引入了 using 关键字用于定义模板别名。using 关键字提供了一种更简洁、易读的方式来定义类型别名,特别是在使用模板时。

using alias_name = type;

alias_name 是你为类型定义的别名,而 type 是你要为其创建别名的类型。using 语句可以用于定义任何类型的别名,包括模板类型。

typedef 可以创建基本类型、指针类型、复合类型和函数指针类型的别名。

using 在 C++11 引入之后,成为了更通用的替代方案,可以用于创建类型别名、模板别名以及模板别名模板。

差别在于,新语法可用于模板部分具体化,但typedef不能

template<typename T>
    using arr12 = std::arry<T,12>

对于如下声明:

std::array<double,12> a1;
std::array<std::string,12> a2;

可以使用上述具体化模板声明:

arr12<double> a1;
arr12(std::string) a2;
  • nullptr

nullptr 是 C++11 中引入的空指针常量,用于代表空指针。它是一个特殊的字面值,可以被赋值给指针类型,而且不会与整数进行混淆。nullptr 用于替代传统的 NULL 宏,NULL 通常被定义为 0 或者 (void*)0。因为 NULL 的定义可能是 0 或者指针类型的零值,所以在某些情况下,使用 NULL 可能会引起歧义,特别是在函数重载时。

nullptr 的引入解决了这个问题,它是一个明确的指针值,可以用于初始化任何指针类型,而不会与整数进行混淆。

二:智能指针

智能指针是 C++ 中用于管理动态内存的一种工具,它们可以自动管理内存的生命周期,从而减少内存泄漏和悬挂指针等问题。智能指针在 C++11 引入标准库之后变得非常流行。

  1. std::unique_ptr

    • std::unique_ptr 是一种独占所有权的智能指针,它确保在其生命周期结束时自动释放所管理的对象。
    • 每个 std::unique_ptr 拥有对其所指向对象的唯一所有权,当 std::unique_ptr 被销毁时,它会自动释放其所管理的对象。
    • 不能进行拷贝操作,但可以进行移动操作。
  2. std::shared_ptr

    • std::shared_ptr 是一种共享所有权的智能指针,它允许多个指针共享对同一对象的所有权。
    • std::shared_ptr 使用引用计数来跟踪有多少个 std::shared_ptr 共享同一对象。当引用计数为零时,对象会被自动释放。
    • 拷贝 std::shared_ptr 会增加引用计数,而销毁或者重置 std::shared_ptr 则会减少引用计数。
  3. std::weak_ptr

    • std::weak_ptr 是一种弱引用智能指针,它不会增加对象的引用计数,也不会拥有对象的所有权。
    • 通常与 std::shared_ptr 一起使用,用于解决 std::shared_ptr 的循环引用问题。
    • 可以通过 std::weak_ptr 创建 std::shared_ptr,但需要检查 std::weak_ptr 是否过期(即底层对象是否已被释放)。

三:类的修改

  • explicit

explicit 关键字用于防止隐式转换和复制初始化。它可以应用于单参数构造函数和转换函数。

#include <iostream>

class MyClass {
public:
    explicit MyClass(int x) : data(x) {}

    int getData() const { return data; }

private:
    int data;
};

void process(const MyClass& obj) {
    std::cout << "Data: " << obj.getData() << std::endl;
}

int main() {
    MyClass obj1(42); // 直接初始化,正常
    process(obj1);    // 调用 process 函数,正常

    // MyClass obj2 = 42; // 错误!explicit 构造函数禁止复制初始化
    MyClass obj2 = MyClass(42); // 正确,使用直接初始化
    process(obj2);              // 调用 process 函数,正常

    return 0;
}

explicit 阻止了 MyClass 的单参数构造函数被用于隐式转换,从而增强了代码的清晰度和安全性。

  • 类内成员初始化

类内成员初始化是在 C++11 引入的特性,允许在类定义中直接初始化成员变量。这种方式可以确保成员变量在对象创建时就被初始化,提高了代码的可读性和可维护性。

#include <iostream>

class MyClass {
public:
    // 类内初始化成员变量
    int data = 0;
    double value = 3.14;

    // 构造函数
    MyClass(int d) : data(d) {} // 对于没有在类内初始化的成员变量,可以在构造函数中进行初始化
};

int main() {
    MyClass obj(42);
    
    std::cout << "Data: " << obj.data << std::endl;   // 输出:Data: 42
    std::cout << "Value: " << obj.value << std::endl; // 输出:Value: 3.14

    return 0;
}

四:模板和STL的修改

为改善模板和标准模板库的可用性。

  • for循环修改

对于内置数组以及包含方法begin()和end()的类(如std::string)和STL容器,基于范围的for循环可简化编写循环的工作。

如要修改可以使用引用类型。

#include <iostream>
#include <vector>
#include <string>

int main() {
    // 修改内置数组
    int arr[] = {1, 2, 3, 4, 5};
    for (int &x : arr) {
        x *= 2; // 将数组中的每个元素乘以 2
    }

    // 修改 std::string
    std::string str = "hello";
    for (char &c : str) {
        c = toupper(c); // 将字符串中的每个字符转换为大写形式
    }

    // 修改 STL 容器(例如 std::vector)
    std::vector<int> vec = {1, 2, 3, 4, 5};
    for (auto &num : vec) {
        num *= 2; // 将容器中的每个元素乘以 2
    }

    // 输出修改后的结果
    for (const auto &x : arr) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    std::cout << str << std::endl;

    for (const auto &num : vec) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}
  • 新的STL容器

C++ 新的 STL(标准模板库)容器包括一些在 C++11 标准中引入的以及后续标准中新增的容器。

  1. std::array

    1. 固定大小的数组,与内置数组类似,但提供了更多的功能和安全性。
    2. 它具有固定的大小,在编译时就确定了,因此不支持动态大小调整。
  2. std::forward_list

    1. 单向链表,与 std::list 不同,它只能从头到尾进行迭代,无法逆向迭代。
    2. std::forward_list 在某些情况下比 std::list 更加高效,尤其是对于大量的插入和删除操作。
  3. std::unordered_setstd::unordered_map

    1. 哈希集合和哈希映射,分别对应于 std::set 和 std::map 的无序版本。
    2. 它们使用哈希表来实现,具有 O(1) 的平均插入、查找和删除时间复杂度,但不保证元素的顺序。
  4. std::unordered_multisetstd::unordered_multimap

    1. 允许重复键的哈希集合和哈希映射,分别对应于 std::multiset 和 std::multimap 的无序版本。
    2. 允许插入相同键的多个副本,不保证元素的顺序。
  5. std::tuple

    1. 元组,可以存储多个不同类型的值,并且可以在编译时或运行时访问这些值。
    2. 元组的大小和类型在编译时确定,提供了一种方便的方式来处理多个值。
  6. std::array_viewstd::span(C++20):

    1. 提供了对连续内存区域的非拥有式访问,允许安全地查看数组或容器的一部分,而不复制数据。
    2. std::span 在 C++20 中引入,提供了更多的功能和灵活性。
  • 新的STL方法

cbegin()cend() 返回的是常量迭代器,而 begin()end() 返回的是普通迭代器。主要区别在于:

  1. cbegin() 和 cend()

    • 返回常量迭代器。
    • 用于遍历容器中的元素,但不能修改这些元素。
    • cbegin() 返回指向容器第一个元素的常量迭代器。
    • cend() 返回指向容器尾后位置的常量迭代器。
  2. begin() 和 end()

    • 返回普通迭代器。
    • 可以用于遍历容器中的元素,并且可以修改这些元素。
    • begin() 返回指向容器第一个元素的迭代器。
    • end() 返回指向容器尾后位置的迭代器。
  3. crbegin() 和 crend()

    • 返回常量逆向迭代器。
    • 用于逆序遍历容器中的元素,并且不能修改这些元素。
    • crbegin() 返回指向容器最后一个元素的常量逆向迭代器。
    • crend() 返回指向容器起始位置的常量逆向迭代器。
  4. rbegin() 和 rend()

    • 返回普通逆向迭代器。
    • 用于逆序遍历容器中的元素,并且可以修改这些元素。
    • rbegin() 返回指向容器最后一个元素的逆向迭代器。
    • rend() 返回指向容器起始位置的逆向迭代器。
#include <iostream>
#include <vector>

int main() {
    // 创建一个vector
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 使用 cbegin() 和 cend() 遍历容器中的元素(不修改元素)
    std::cout << "Using cbegin() and cend(): ";
    for (auto it = vec.cbegin(); it != vec.cend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 使用 begin() 和 end() 遍历容器中的元素(可以修改元素)
    std::cout << "Using begin() and end(): ";
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        *it *= 2; // 修改元素的值
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 使用 crbegin() 和 crend() 逆序遍历容器中的元素(不修改元素)
    std::cout << "Using crbegin() and crend(): ";
    for (auto it = vec.crbegin(); it != vec.crend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 使用 rbegin() 和 rend() 逆序遍历容器中的元素(可以修改元素)
    std::cout << "Using rbegin() and rend(): ";
    for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
        *it *= 2; // 修改元素的值
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}
  • >>

为避免与运算符>>混淆,C++11要求在声明嵌套模板时使用空格将尖括号分开

std::vector<std::pair<int, std::string> > myVector; // 正确
  • 左值引用

传统C++引用(现称左值引用)使得标识符关联到左值,

左值引用的声明

int x = 5;
int& refX = x;  // refX 是对 x 的左值引用

左值引用作为函数参数

左值引用经常用作函数的参数,可以实现按引用传递,避免不必要的复制。

void increment(int& num) {
    num++;  // 修改传入的参数
}

int main() {
    int value = 10;
    increment(value);  // 传入 value 的引用
    std::cout << value << std::endl;  // 输出 11,因为 value 已被增加
    return 0;
}

左值引用和赋值

int x = 5;
int y = 10;
int& ref = x;  // ref 引用 x
ref = y;       // 将 x 的值改为 y 的值,即 x 变为 10

虽然引用和取地址都可以用于访问对象,但它们的实现方式和用途不同:

  • 引用是对象的别名,提供了对对象的另一种访问方式,更方便、更安全地操作对象。
  • 取地址是获取对象在内存中的地址,返回的是指向目标对象的指针,可以通过指针来直接访问或修改对象的值,但需要注意指针的正确使用和管理。

引用示例

#include <iostream>

int main() {
    int x = 10;
    int& ref = x;  // 定义引用 ref,绑定到变量 x

    std::cout << "x 的值为:" << x << std::endl;
    std::cout << "ref 的值为:" << ref << std::endl;

    ref = 20;  // 通过引用修改 x 的值

    std::cout << "修改后,x 的值为:" << x << std::endl;
    std::cout << "修改后,ref 的值为:" << ref << std::endl;

    return 0;
}

取地址示例

#include <iostream>

int main() {
    int x = 10;
    int* ptr = &x;  // 取得变量 x 的地址,并将其保存到指针 ptr 中

    std::cout << "x 的值为:" << x << std::endl;
    std::cout << "ptr 指向的值为:" << *ptr << std::endl;

    *ptr = 20;  // 通过指针修改 x 的值

    std::cout << "修改后,x 的值为:" << x << std::endl;
    std::cout << "修改后,ptr 指向的值为:" << *ptr << std::endl;

    return 0;
}
  • 左值引用必须绑定到一个具有持久性的对象,因此不能绑定到临时对象(右值)。
  • 左值引用一般用于实现按引用传递,可以在函数内部修改传入的参数,而不是复制参数的副本。
  • 右值引用

右值引用是 C++11 引入的新特性,用于实现移动语义和完美转发,主要用于优化对象的拷贝和移动操作。

右值引用的声明方式是在类型后面加上 &&,例如 int&& 表示一个右值引用类型。

int&& rvalue_ref = 5; // 右值引用绑定到临时对象 5

右值引用主要用于绑定到临时对象(右值),通常用于移动语义和完美转发。

int&& rvalue_ref = 5; // 右值引用绑定到临时对象 5

int x = 10;
int&& rvalue_ref2 = std::move(x); // std::move 将左值转换为右值引用

std::move 是一个 C++ 中的函数模板,主要用于将对象转换为右值引用,从而支持移动语义。它的主要作用有两个:

  1. 标识对象为右值引用: std::move 将对象转换为右值引用,即使原本是左值,这样就可以在移动构造函数和移动赋值运算符中使用。这使得我们可以使用移动语义,将资源从一个对象转移到另一个对象,而不是进行深拷贝。这样可以提高程序的性能和效率。

  2. 避免不必要的拷贝: 通过将对象转换为右值引用,std::move 告诉编译器该对象可以被移动而不是复制,从而避免不必要的拷贝操作。这对于大型对象或者资源管理类(如动态分配的内存、文件句柄等)尤其有用,因为移动操作通常比复制操作更加高效。

右值引用可以用于传递临时对象或者转移对象的所有权。

void process(int&& data) {
    // 处理右值引用绑定的临时对象
}

int main() {
    process(10); // 传递临时对象
    int x = 20;
    process(std::move(x)); // 转移对象的所有权

    return 0;
}

移动语义

传统的拷贝操作会导致对象的深拷贝,即复制对象的所有内容,包括动态分配的内存资源,这在处理大型对象时可能效率低下。而移动语义允许在资源管理类中,将资源的所有权从一个对象转移到另一个对象,而不需要进行深拷贝,从而提高了效率。

移动语义的关键在于利用右值引用来识别临时对象(右值),然后通过移动构造函数或者移动赋值运算符来“窃取”这些临时对象的资源,而不是像拷贝构造函数那样创建新的资源副本。

移动语义的实现通常使用 std::move 来将对象转换为右值引用,以便移动构造函数和移动赋值运算符能够正确地被调用。通过移动语义,可以有效地避免不必要的资源复制,提高程序的性能

完美转发

完美转发是一种技术,允许我们在函数中将参数以相同的方式传递给其他函数,保持参数的值类型不变。它通过右值引用和模板来实现。std::forward 是实现完美转发的关键,它能够在保持参数类型不变的同时,将参数转发给其他函数。

 简单的参数传递:如果你只是简单地将参数传递给其他函数,而不需要保留其值类别(即不需要完美转发),那么你可以直接使用传递给你的参数。在这种情况下,不使用 std::forward 是可以的。

template<typename T>
void foo(T arg) {
    bar(arg);  // 没有保留参数的值类别的必要性
}

 复杂的参数传递:但是,在需要将参数完美转发给其他函数的情况下,特别是在涉及到重载或泛型编程时,使用 std::forward 更为安全。这样可以确保参数的原始值类别被保留,从而正确地调用相关函数。使用 std::forward 可以避免意外地触发拷贝构造函数或移动构造函数,从而提高了代码的效率和安全性。

template<typename T>
void foo(T&& arg) {
    bar(std::forward<T>(arg));  // 保留参数的值类别
}
#include <iostream>
#include <utility> // for std::move, std::forward

template<typename T>
class MoveOnlyVector {
private:
    T* data;
    size_t capacity;
    size_t size;

public:
    // 默认构造函数
    MoveOnlyVector() : data(nullptr), capacity(0), size(0) {}

    // 析构函数
    ~MoveOnlyVector() {
        delete[] data;
    }

    // 移动构造函数
    MoveOnlyVector(MoveOnlyVector&& other) noexcept 
        : data(std::exchange(other.data, nullptr)), 
          capacity(std::exchange(other.capacity, 0)), 
          size(std::exchange(other.size, 0)) {}

    // 移动赋值运算符
    MoveOnlyVector& operator=(MoveOnlyVector&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = std::exchange(other.data, nullptr);
            capacity = std::exchange(other.capacity, 0);
            size = std::exchange(other.size, 0);
        }
        return *this;
    }

    // 添加元素
    template<typename U>
    void push_back(U&& value) {
        if (size >= capacity) {
            // 扩展容量
            size_t new_capacity = (capacity == 0) ? 1 : 2 * capacity;
            T* new_data = new T[new_capacity];
            for (size_t i = 0; i < size; ++i) {
                new_data[i] = std::move(data[i]);
            }
            delete[] data;
            data = new_data;
            capacity = new_capacity;
        }
        data[size++] = std::forward<U>(value);
    }
};

int main() {
    MoveOnlyVector<std::string> vec;

    // 添加元素
    vec.push_back("hello");
    vec.push_back(std::string("world"));

    return 0;
}

五:绑定器和函数对象

  • function

std::function 是C++11中的一个模板类,用于封装任意可调用对象,包括函数指针、函数对象、成员函数指针、Lambda表达式等。它提供了一种统一的方式来处理不同类型的可调用对象,并可以在运行时确定其类型。

#include <functional>

// 定义一个函数
int add(int x, int y) {
    return x + y;
}

// 使用 std::function 封装一个可调用对象
std::function<int(int, int)> func = add;

// 调用封装的函数
int result = func(3, 4);  // result = 7

 std::function 的模板参数是函数的签名,它可以用来存储具有相同参数和返回类型的任意可调用对象。

  • bind

std::bind 是C++11中的一个函数模板,用于部分应用函数参数或重新排序函数参数。它允许您在调用函数时固定某些参数的值,从而创建一个新的可调用对象。绑定器(对STL中bind1st和bind2nd的升级,结合二元函数对象-》一元函数对象)

#include <functional>

// 定义一个函数
int add(int x, int y) {
    return x + y;
}

auto add_five = std::bind(add, std::placeholders::_1, 5);
int result = add_five(3);  // result = 3 + 5 = 8

在这个例子中,std::bindadd 函数的第一个参数绑定为占位符 std::placeholders::_1,并将第二个参数固定为 5。返回的 add_five 可调用对象只有一个参数,当调用它时,它会将传递给它的参数与之前绑定的参数一起传递给 add 函数。

  •  Lambda表达式

Lambda表达式是C++11中引入的一种匿名函数语法,它允许您在需要函数对象的地方内联定义函数。Lambda表达式可以捕获外部变量,并具有非常灵活的语法。

#include <iostream>

int main() {
    int x = 3;
    int y = 4;

    // 使用Lambda表达式定义一个函数对象
    auto func = [x, y](int a, int b) {
        return a * x + b * y;
    };

    int result = func(1, 2);  // result = 1 * 3 + 2 * 4 = 11
    std::cout << "Result: " << result << std::endl;

    return 0;
}

六:C++语言级别支持的多线程编程

C++的标准库在C++11版本中引入了多线程支持,这为跨平台多线程编程提供了一个统一的接口。这意味着你可以使用C++标准库中的多线程API来编写跨平台的多线程应用程序,而无需依赖于特定的操作系统API(如Windows的CreateThread、Linux的pthread_createclone)。

1. std::thread

std::thread是C++11中提供的类,它封装了操作系统特定的线程创建和管理方式。这使得你可以在不同的操作系统上使用相同的代码创建和管理线程。

#include <iostream>
#include <thread>

// 一个简单的线程函数
void threadFunction() {
    std::cout << "Thread is running" << std::endl;
}

int main() {
    // 创建一个新线程并运行 threadFunction
    std::thread myThread(threadFunction);

    // 等待线程结束
    myThread.join();

    return 0;
}

在这个例子中,创建了一个线程,执行threadFunction函数,然后等待该线程结束。std::thread类允许你创建、启动、加入、分离线程等。

2. std::mutex 和 线程同步:

多线程编程通常需要考虑同步和共享资源的访问。C++标准库提供了各种同步机制,如互斥锁、条件变量等。

#include <iostream>
#include <thread>
#include <mutex>

// 共享资源
int counter = 0;
std::mutex mtx;

void incrementCounter() {
    std::lock_guard<std::mutex> lock(mtx);
    counter++;
}

int main() {
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);

    t1.join();
    t2.join();

    std::cout << "Counter: " << counter << std::endl;

    return 0;
}

使用std::mutex来保护共享资源counter,确保在多线程环境下不会发生数据竞争

3. 其他同步机制:

除了std::mutex外,C++标准库还提供了std::condition_variablestd::futurestd::promise等,用于实现更复杂的线程同步和通信。

假设正在开发一个网络服务器,需要处理来自多个客户端的请求。我们将使用多线程来同时处理这些请求,以提高服务器的性能和并发能力。

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <queue>
#include <chrono>

// 定义一个任务结构体,用于模拟客户端请求
struct Task {
    int id;
    Task(int _id) : id(_id) {}
};

// 定义一个线程安全的队列,用于存储待处理的任务
template<typename T>
class SafeQueue {
private:
    std::queue<T> queue_;
    std::mutex mutex_;
public:
    void push(const T& item) {
        std::lock_guard<std::mutex> lock(mutex_);
        queue_.push(item);
    }

    bool try_pop(T& item) {
        std::lock_guard<std::mutex> lock(mutex_);
        if (queue_.empty()) {
            return false;
        }
        item = queue_.front();
        queue_.pop();
        return true;
    }
};

// 定义一个处理请求的函数
void processRequest(SafeQueue<Task>& tasks, int threadId) {
    while (true) {
        Task task(0);
        if (tasks.try_pop(task)) {
            // 模拟处理请求的过程
            std::cout << "Thread " << threadId << " processing task " << task.id << std::endl;
            // 模拟请求处理时间
            std::this_thread::sleep_for(std::chrono::seconds(1));
        } else {
            // 如果队列为空,则线程等待新的任务到来
            std::this_thread::yield();
        }
    }
}

int main() {
    const int numThreads = 4;
    SafeQueue<Task> taskQueue;

    // 创建多个线程来处理请求
    std::vector<std::thread> threads;
    for (int i = 0; i < numThreads; ++i) {
        threads.emplace_back(processRequest, std::ref(taskQueue), i);
    }

    // 模拟生成一些任务并将其放入队列中
    for (int i = 1; i <= 10; ++i) {
        taskQueue.push(Task(i));
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }

    // 等待所有线程完成任务
    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/576679.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

不对称催化(三)- 动态动力学拆分动态动力学不对称转化

一、动力学拆分的基本概念&#xff1a; 动力学拆分的最大理论产率为50%&#xff0c;通过的差异可以将两个对映异构体转化为不同构型的产物&#xff0c;通常情况下使用两个不同反应路径来实现。但是化学家们提供了一个更加实用的方法&#xff0c;通过底物的构型变化实现高于50%的…

【Leetcode】377. 组合总和 Ⅳ

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 给你一个由 不同 整数组成的数组 n u m s nums nums&#xff0c;和一个目标整数 t a r g e t target target 。请你从 n u m s nums nums 中找出并返回总和为 t a r g e t targ…

MySQL随便聊----之MySQL的调控按钮-启动选项和系统变量

-------MySQL是怎么运行的 基本介绍 如果你用过手机&#xff0c;你的手机上一定有一个设置的功能&#xff0c;你可以选择设置手机的来电铃声、设置音量大小、设置解锁密码等等。假如没有这些设置功能&#xff0c;我们的生活将置于尴尬的境地&#xff0c;比如在图书馆里无法把手…

炫云云渲染:免费体验与高性价比的首选,设计师们的渲染利器

使用云渲染是要收费的&#xff0c;如果你是第一次使用&#xff0c;是可以白嫖一波云渲染的&#xff0c;所有的云渲染都会或多或少送一些渲染券&#xff0c;你可以用它们送的渲染券免费渲一波图。但是不能一直白嫖&#xff0c;再次注册账号人家就不会送体验券了&#xff0c;有些…

茴香豆:搭建你的RAG智能助理-作业三

本次课程由书生浦语社区贡献者【北辰】老师讲解【茴香豆&#xff1a;搭建你的 RAG 智能助理】课程。分别是&#xff1a; RAG 基础介绍茴香豆产品简介使用茴香豆搭建RAG知识库实战 课程视频&#xff1a;https://www.bilibili.com/video/BV1QA4m1F7t4/ 课程文档&#xff1a;ht…

为什么近年来机器学习这么火!!

机器学习&#xff08;Machine Learning&#xff09;是一种人工智能&#xff08;AI&#xff09;的分支&#xff0c;它让计算机能够通过数据学习和改进&#xff0c;而无需明确的编程。这意味着机器学习系统可以从经验中学习&#xff0c;逐步提高其性能。它基于统计学和数学算法&a…

OpenHarmony实战开发-按钮 (Button)

Button是按钮组件&#xff0c;通常用于响应用户的点击操作&#xff0c;其类型包括胶囊按钮、圆形按钮、普通按钮。Button做为容器使用时可以通过添加子组件实现包含文字、图片等元素的按钮。具体用法请参考Button。 创建按钮 Button通过调用接口来创建&#xff0c;接口调用有…

Unity入门实践小项目

必备知识点 必备知识点——场景切换和游戏退出 必备知识点——鼠标隐藏锁定相关 必备知识点——随机数和Unity自带委托 必备知识点——模型资源的导入 实践项目 需求分析 UML类图 代码和资源导入 开始场景 场景装饰 拖入模型和添加脚本让场景动起来 开始界面 先用自己写的GUI…

贪吃蛇撞墙功能的实现 和自动行走刷新地图 -- 第三十天

1.撞墙 1.1最初的头和尾指针要置为空&#xff0c;不然是野指针 1.2 在增加和删除节点后&#xff0c;判断是否撞墙&#xff0c;撞墙则初始话蛇 1.3在撞墙后初始化蛇&#xff0c;如果头不为空就撞墙&#xff0c;得定义临时指针指向头&#xff0c;释放头节点 2.自动刷新地图 2.1…

4 -26

4-26 1 英语单词100个一篇六级翻译 2 div 4 补题目 3 概率论期中卷子一张&#xff0c;复习复习。 4 备课ing 晚上出去炫饭&#xff0c;串串香&#xff0c;无敌了。 中间一些模拟题是真的恶心&#xff0c;思维题是真的想不到&#xff0c;感觉自己就是一个废物呢。 1.是将一个数…

JUC之线程、线程池

一、start与run方法 start方法开启一个新线程&#xff0c;异步执行。 run方法同步执行&#xff0c;不会产生新的线程。 start方法只能执行一次&#xff0c;run方法可以执行多次。 二、一些方法 sleep() 线程睡眠 两种方式调用&#xff1a; Thread.sleep(1000);TimeUnit.…

Kafka 3.x.x 入门到精通(06)Kafka进阶

Kafka 3.x.x 入门到精通&#xff08;06&#xff09;——对标尚硅谷Kafka教程 3. Kafka进阶3.1 Controller选举3.2 Broker上线下线3.3 数据偏移量定位3.4 Topic删除3.5 日志清理和压缩3.7 页缓存3.8 零拷贝3.9 顺写日志3.10 Linux集群部署3.10.1 集群规划3.10.2 安装虚拟机(略)3…

Ubuntu下载的nginx的位置

位置在/etc/nginx 启动nginx systemctl status nginx上面的命令不合适&#xff0c;就重启nginx sudo service nginx restart 关闭nginx nginx -s stop Ubuntu默认的html地址在该文件夹中的default中&#xff1a; /etc/nginx/sites-available if ($http_host ~* "^(w…

怎么用AI绘画进行人物修复?

用过AI绘画生成人物图片的朋友们是不是都碰到过这样的问题&#xff1a;诡异的造型、崩坏的五官、离谱的手指头、乱七八糟的背景...指望AI一次性生成百分百完美的图貌似有点难啊。 现在AI绘画有了【脸部修复】【手部修复】功能&#xff0c;就能够轻松解决这些的问题了&#xff0…

ESLint 、 e2e test 学习

Lint和Format的区别&#xff1a; Lint只会告诉你代码中的错误或者不符合规范的地方&#xff0c;而Format是用来对格式作调整的 HTML/tpl&#xff1a;HTMLLint CSS/SCSS&#xff1a;Stylelint JS/JSX&#xff1a;Eslint JSLint&#xff1a;古老&#xff0c;不能配置和扩展JSHin…

十几款必备AI写作软件!涵盖国内国外,免费在线一键生成原创文案文章!

今天&#xff0c;就让我带您领略市面上那些备受瞩目的10款AI写作神器&#xff0c;一同探索哪款工具 将成为您创作旅程中的得力助手。首先&#xff0c;让我们揭开这些A1写作工具的神秘面纱 深入了解它们的基本功能。这些工具如同智慧之笔&#xff0c;能够迅速为您生成文章、段 落…

工作记录:vue-grid-layout 修改 margin 导致 item 高度剧烈变化

问题 用 vue-gird-layout 时发现&#xff0c;当改变 margin 值时&#xff0c;item 的尺寸也会跟着变化。 如下图&#xff1a;row height 和每个 item 的 h 都保持不变。修改 margin-y&#xff0c;item 的实际高度也跟着变了&#xff1a; 原因 研究了一番&#xff0c;发现原…

MySql 主从同步-在原来同步基础上增加历史数据库

在MySql已经主从同步的后&#xff0c;由于有新的需求再增加1个历史数据库&#xff0c;要改原来的1个变成现在的2个数据库。在官网并没有找到类似的场景&#xff08;官方同步多个数据是从一开始就设置&#xff0c;不是后续增加的&#xff09;&#xff0c;只能结合以往的经验自己…

《HCIP-openEuler实验指导手册》1.6 Apache静态资源配置

知识点 常用用途&#xff1a; 软件仓库镜像及提供下载服务&#xff1a; 配置步骤 删除网站主目录中的文件&#xff08;本实验机目录为/home/source ip为192.168.12.137 端口为81&#xff09; cd /home/source rm -rf *在主目录中新建6个文件夹如下图 mkdir test{1..6}新建…

VTK----VTK数据结构详解3(代码篇)

上篇文章&#xff08;VTK----VTK数据结构详解&#xff08;计算机篇&#xff09;-CSDN博客&#xff09;从计算机数据结构&#xff08;数组、链表等&#xff09;的角度对数据数组、数据对象、数据属性的实现原理进行了说明&#xff0c;下面从代码的层面详细说明它们的使用及相关实…
最新文章