当前位置: 首页 > news >正文

C11补充

目录

    • async
    • 可变参数
    • 完美转发、右值引用、引用折叠、std::forward
    • tuple
      • 特性:
    • std::apply
    • invoke_result

async

async中封装promise,所以只需要与future配合使用即可

template <class Fn, class... Args> 
std::future<typename std::result_of<Fn(Args...)>::type> 
async(std::launch policy, Fn&& f, Args&&... args); 
  • std::async会创建一个异步任务并返回一个std::future对象,该对象可用于获取异步任务的返回值
  • policy是异步任务启动策略,f是要执行的函数或可调用对象(如lambda表达式、函数指针等),args是传递给f的参数。

启动策略std::launch有以下两种:

强制异步执行:使用std::launch::async强制在新线程中异步执行任务

  • async以std::launch::async策略启动compute函数,compute函数模拟一个耗时2秒的任务。
  • 主线程继续执行打印“Waiting for result…”,然后通过fut.get()等待异步任务完成并获取结果。

#include <iostream>
#include <future>
#include <chrono>int compute() {std::this_thread::sleep_for(std::chrono::seconds(2));return 42;
}int main() {std::future<int> fut = std::async(std::launch::async, compute);std::cout << "Waiting for result...\n";int result = fut.get();std::cout << "Result: " << result << std::endl;return 0;
}

延迟执行:使用std::launch::deferred延迟任务执行

  • 任务会在调用future.wait()或future.get()时才执行,并且不会创建新线程
  • async使用std::launch::deferred策略创建延迟任务,调用future.get()时才会执行Func函数,且通过打印线程ID可发现,它是在主线程中执行,没有创建新线程。
#include <iostream>
#include <future>
#include <chrono>int Func() {std::cout << "Func Thread id = " << std::this_thread::get_id() << std::endl;return 0;
}int main() {std::cout << "Main Thread id = " << std::this_thread::get_id() << std::endl;auto future = std::async(std::launch::deferred, Func);std::cout << "Result =  " << future.get() << std::endl;return 0;
}

可变参数

在模板 template<class F, class… Args> 中,F 和 Args 的设计是为了支持通用函数调用

  • F 是可调用对象类型
    例如:函数指针、函数对象(functor)、lambda 表达式、std::function 等。
  • Args是 F 的参数类型
    例如:F 是 void func(int, double),那么 Args 就是 int 和 double。
  • std::invoke_result<F, Args…>::type 会推导出 f(args…) 的返回类型(如 void)。

设计原因

  • 完美转发:F&& f 和 Args&&… args 使用引用折叠(reference collapsing)和完美转发,确保参数可以正确传递(左值、右值、const、非 const 等)。
  • 类型推导:std::invoke_result<F, Args…>::type 会自动推导 f(args…) 的返回类型,无需手动指定。

完美转发、右值引用、引用折叠、std::forward

  1. 什么是引用?
    在C++中,引用是已存在变量的别名。比如:
    int x = 10;
    int& y = x; // y 是 x 的引用,y 和 x 指向同一个值
    int& 表示 y 是一个左值引用,只能绑定到左值(如变量 x)。
    左值:可以取地址的、有名字的对象(如变量)。
    右值:不能取地址的、临时的对象(如字面量 10 或表达式 x + y)。
  1. 什么是右值引用?
    C++11 引入了右值引用,专门用于绑定右值:
void process(int&& x) { // 右值引用std::cout << "Right value: " << x << std::endl;
}
int main() {process(42); // 可以绑定右值(字面量)// process(x); // 错误:不能绑定左值
}
int&& 表示右值引用,只能绑定到右值(如 42)。
  1. 什么是完美转发?
    假设我们有一个函数 wrapper,它调用另一个函数 targetFunction:
void targetFunction(int& x) {std::cout << "Lvalue: " << x << std::endl;
}void targetFunction(int&& x) {std::cout << "Rvalue: " << x << std::endl;
}// 目标:让 wrapper 调用 targetFunction,并保持参数的左值/右值属性
void wrapper(int& x) {targetFunction(x); // 调用左值版本
}void wrapper(int&& x) {targetFunction(x); // 调用右值版本
}int main() {int a = 10;wrapper(a);      // 调用 wrapper(int&)wrapper(20);     // 调用 wrapper(int&&)
}

问题:如果 wrapper 的参数很多,写多个重载会很麻烦。
解决方案:用模板 + 完美转发。

  1. 模板 + 完美转发
template<typename T>
void wrapper(T&& x) {targetFunction(std::forward<T>(x)); // 关键!
}int main() {int a = 10;wrapper(a);      // 调用 targetFunction(int&)wrapper(20);     // 调用 targetFunction(int&&)
}

T&& 是转发引用,可以是左值引用或右值引用。
std::forward(x) 会根据 T 的类型决定是否转换 x 为右值:
如果 T 是 int&(左值引用),std::forward 保持左值。
如果 T 是 int&&(右值引用),std::forward 转换为右值。

  1. 引用折叠
    在模板中,引用类型可以嵌套,比如 T& & 或 T&& &。引用折叠规则:

T& & → T&(左值引用)
T& && → T&(左值引用)
T&& & → T&(左值引用)
T&& && → T&&(右值引用)

template<typename T>
void foo(T&& param) {// 如果用 int& 调用,T 会推导为 int&// 如果用 int 调用,T 会推导为 int&&
}int main() {int x = 10;foo(x);    // T = int&, param 类型是 int& & → int&foo(20);   // T = int&&, param 类型是 int&& && → int&&
}
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args) -> 
std::future<typename std::invoke_result<F,Args...>::type>;

F&& f 和 Args&&… args 是转发引用,可以接受左值或右值。
std::forward(f) 和 std::forward(args)… 会根据 F 和 Args 的类型决定是否转换为右值,从而完美转发参数。
这样,enqueue 可以接受任何可调用对象(函数、lambda、对象等)及其参数,并保持它们的左值/右值属性。

tuple

std::tuple 提供了一种灵活存储不同类型数据的方式,是C++标准库中的一个模板类,用于存储多个不同类型的元素,位于头文件中。
其定义为template< class… Types > class tuple;
Types…是一个可变参数模板,表示std::tuple可以包含任意数量类型不同的元素 。
构造与初始化:

  • 默认构造:创建一个所有元素都使用默认构造函数的tuple对象。
    例如std::tuple<int, std::string, double> t;
    t的元素默认初始化为0(int),空字符串(std::string),0.0(double)
  • 直接初始化:使用括号直接提供元素的值。
    例如:std::tuple<int, std::string, double> t(1, “Hello”, 3.14);
  • 列表初始化(C++11及以上):使用花括号进行初始化
    例如:auto t = std::tuple<int, std::string, double>{1, “Hello”, 3.14};
  • 更常用的方式是搭配std::make_tuple,
    例如:auto t = std::make_tuple(1, “Hello”, 3.14);
  • 通过std::make_tuple可以自动推导类型并构造一个std::tuple对象
    例如auto my_tuple = std::make_tuple(10, 3.14, “Hello”);
    成员函数和访问元素:

std::get:用于通过类型或索引访问std::tuple的元素。
例如std::tuple<int, std::string, double> t(1, “Hello”, 3.14);
int first = std::get<0>(t);
从C++14开始,如果std::tuple中的类型是唯一的,还可以通过类型访问,
如std::string second = std::getstd::string(t);
不过通过类型访问可能引发歧义,更推荐使用索引访问 。

std::tuple_size和std::tuple_element:
这两个模板类分别用于获取std::tuple的大小(即元素的数量)和元素的类型。
例如constexpr std::size_t size = std::tuple_size<decltype(t)>::value; 可得到tuple的元素个数,using FirstType = std::tuple_element<0, decltype(t)>::type; 可得到第一个元素的类型

特性:

异构性:可以包含不同类型的元素
如std::tuple<int, char, std::string, bool> my_tuple(42, ‘A’, “World”, true); 。
固定大小:大小在编译时确定,不能动态改变 。
比较操作:支持比较操作符,如==、!=、<等,会逐个元素进行比较 。
解构:可以使用std::tie将std::tuple的元素解构到单独的变量中。例如int a; double b; std::string c; std::tie(a, b, c) = my_tuple; 。

应用场景:
返回多个值:函数可以返回一个std::tuple,从而返回多个值。
例如std::tuple<int, double, std::string> get_values()
{ return std::make_tuple(10, 3.14, “Hello”); } ,
调用时auto result = get_values();
int a = std::get<0>(result); 。
存储异构数据:可用于存储不同类型的数据,例如在需要传递多个不同类型参数时 。
作为容器的元素:可以作为容器(如std::vector)的元素,用于存储复杂的结构 。

std::apply

定义:是C++17中引入的一个函数模板,位于头文件中。

语法为
template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t); 

其中F是可调用对象的类型,Tuple是一个std::tuple或类似的可转换为元组类型的对象 。
作用:
允许以元组的方式将参数传递给一个可调用对象(函数、函数指针、成员函数指针、函数对象等),
将元组t中的元素作为参数传递给可调用对象f并执行它,返回可调用对象的结果 。

示例
std::apply将values元组中的元素解包并作为参数传递给print_values函数执行 。

#include <iostream>
#include <tuple>
void print_values(int a, float b, const std::string& c) {std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
}
int main() {std::tuple<int, float, std::string> values(42, 3.14, "Hello");std::apply(print_values, values);return 0;
}

,而std::apply 则为处理std::tuple 作为函数参数提供了便利 。

invoke_result

  • invoke_result是一个模板类,用于获取调用特定函数对象 或 函数指针后的返回值类型
  • 将 可调用对象类型 和 参数类型 作为模板参数,并提供一个嵌套成员类型,
    表示调用该可调用对象后的返回值类型
  • 在编写模板代码时,使用std::invoke_result可避免重复编写相同的返回值类型推断逻辑,方便获取函数对象或函数指针的返回值类型
  • std::invoke_result<foo, int>::type将返回int类型,
    foo类返回int值,通过std::invoke_result,能轻松获取foo函数对象的返回值类型,而无需手动指定
#include <type_traits> 
#include <iostream> struct foo { int operator()(int x) { return x * 2; } 
}; int main() { std::invoke_result<foo, int>::type result; std::cout << typeid(result).name() << std::endl; return 0; 
} 
http://www.lryc.cn/news/598778.html

相关文章:

  • 力扣20:有效的括号
  • VirtualBox安装Ubuntu 22.04后终端无法打开的解决方案
  • 在 Ubuntu 20.04 上轻松安装和使用中文输入法
  • 离线进行apt安装的过程(在只能本地传输的ubuntu主机上使用apt安装)
  • 秋叶sd-webui频繁出现生成后无反应的问题
  • 11-day08文本匹配
  • 0724 双向链表
  • Unity 进行 3D 游戏开发如何入门
  • iOS网络之异步加载
  • 医疗设备自动化升级:Modbus TCP与DeviceNet的协议协同实践
  • vue3使用异步加载腾讯地图
  • 低速信号设计之 JTAG 篇
  • Spring Bean生命周期七步曲:定义、实例化、初始化、使用、销毁
  • Datawhale AI夏令营学习笔记:大模型微调与数据处理实践
  • 01_FOC学习之先让电机转动起来
  • 长糖链皂苷的生物合成研究进展-文献精读149
  • FreeRTOS—计数型信号量
  • Unity UI的未来之路:从UGUI到UI Toolkit的架构演进与特性剖析(3)
  • 【自动化运维神器Ansible】Ansible常用模块之shell模块详解
  • 深入解析Hadoop NameNode的Full GC问题、堆外内存泄漏及元数据分治策略
  • Lua(数组)
  • DBA常用数据库查询语句(2)
  • 详解FreeRTOS开发过程(六)-- 队列
  • Redis操作
  • PostgreSQL 跨库查询方法
  • CMake ARGV变量使用指南
  • 基于C语言的Zynq SOC FPGA嵌入式裸机设计和开发教程
  • 外企本土化布局对国内连接器企业影响几何?
  • 模型的存储、加载和部署
  • rust-切片类型