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
- 什么是引用?
在C++中,引用是已存在变量的别名。比如:
int x = 10;
int& y = x; // y 是 x 的引用,y 和 x 指向同一个值
int& 表示 y 是一个左值引用,只能绑定到左值(如变量 x)。
左值:可以取地址的、有名字的对象(如变量)。
右值:不能取地址的、临时的对象(如字面量 10 或表达式 x + y)。
- 什么是右值引用?
C++11 引入了右值引用,专门用于绑定右值:
void process(int&& x) { // 右值引用std::cout << "Right value: " << x << std::endl;
}
int main() {process(42); // 可以绑定右值(字面量)// process(x); // 错误:不能绑定左值
}
int&& 表示右值引用,只能绑定到右值(如 42)。
- 什么是完美转发?
假设我们有一个函数 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 的参数很多,写多个重载会很麻烦。
解决方案:用模板 + 完美转发。
- 模板 + 完美转发
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 转换为右值。
- 引用折叠
在模板中,引用类型可以嵌套,比如 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;
}