C++ 可变参数模板
template<typename Callable, typename ... Args>
explicit joining_thread(Callable&& func, Args&& ... args): t(std::forward<Callable>(func), std::forward<Args>(args)...) {}
1. template<typename Callable, typename … Args>
- template<…>: 这是一个模板声明的开始,意味着这个构造函数可以用于多种不同的类型。
- typename Callable:
- typename: 关键字,表明 Callable 是一个类型。
- Callable: 这是一个类型模板参数 (type template parameter)。它代表一个类型,从名字上看,它期望是一个“可调用对象”(如函数指针、函数对象、lambda 表达式等)。
- typename … Args:
- typename: 同上。
- … Args: 这是一个模板参数包 (template parameter pack)。Args 代表零个或多个类型。这意味着构造函数可以接受任意数量和类型的额外参数,这些参数的类型将被打包到 Args 中。
2. explicit joining_thread(Callable&& func, Args&& … args)
- explicit:
- 这是一个关键字,用于修饰构造函数。它阻止编译器进行隐式类型转换。如果一个构造函数可以用单个参数调用,explicit 可以防止这样的代码:joining_thread my_thread = some_function; (如果 some_function 的类型可以转换为 Callable) 被意外地编译通过。强制要求使用直接初始化语法,如 joining_thread my_thread(some_function);。
- joining_thread:
- 这是类的名称,表明这是一个 joining_thread 类的构造函数。
- Callable&& func:
- Callable: 这是上面定义的类型模板参数。
- &&: 这是一个右值引用 (rvalue reference)。然而,当它与一个通过模板类型推导(如这里的 Callable)的类型参数一起使用时,它具有特殊含义,被称为转发引用 (forwarding reference) 或 通用/万能引用 (universal reference)。
- 转发引用 (Forwarding Reference): 它可以绑定到左值(lvalue,有固定内存地址的对象)和右值(rvalue,临时的、即将销毁的对象)。
- func: 这是构造函数的第一个参数名,它代表一个可调用对象。
- Args&& … args:
- Args: 这是上面定义的模板参数包。
- &&: 同样,这里的 && 结合模板参数包 Args 中的每个类型,为每个参数创建转发引用。
- … args: 这是一个函数参数包 (function parameter pack)。它表示构造函数可以接受零个或多个额外的参数,这些参数的名称被打包到 args 中,它们的类型对应于 Args 包中的类型。
3. : t(std::forward<Callable>(func), std::forward<Args>(args)…)
- :: 这是成员初始化列表 (member initializer list) 的开始。构造函数在这里初始化类的成员变量,而不是在构造函数体
{}内部赋值。 - t(…):
- 这表明 joining_thread 类有一个名为 t 的成员变量,并且这个成员变量正在被括号内的参数初始化。
- 从上下文(类名 joining_thread,参数是可调用对象和其参数)来看,t 是一个 std::thread 对象。
- std::forward<Callable>(func):
- std::forward: 这是一个定义在 <utility> 头文件中的标准库函数模板。它的目的是实现完美转发 (perfect forwarding)。
- 完美转发 (Perfect Forwarding): 当你通过转发引用接收参数,并希望将这些参数以其原始的值类别 (value category)(左值或右值)传递给另一个函数时,就需要使用 std::forward。
- 如果 func 最初传递给 joining_thread 构造函数的是一个左值,std::forward<Callable>(func) 会将其作为左值转发。
- 如果 func 最初传递的是一个右值,std::forward<Callable>(func) 会将其作为右值转发(这对于启用移动语义至关重要)。
- <Callable>: 这是传递给 std::forward 的模板参数,指明了 func 被推导出的类型。
- std::forward<Args>(args)…:
- 这部分是对函数参数包 args 进行完美转发。
- std::forward<Args>(args): 对 args 包中的每一个参数应用 std::forward。注意这里 Args 也是一个包。
- … (在 std::forward<Args>(args) 之后): 这是一个包展开 (pack expansion)。它将 std::forward<TypeN>(argN) 这个模式应用到 args 包中的每一个参数上。 例如,如果 Args 是 int, double 且 args 是 10, 3.14,那么这会展开成: std::forward<int>(/*第一个参数值*/), std::forward<double>(/*第二个参数值*/) (实际类型会由编译器推导并放入 Args 包中)
4. {}
- 这是构造函数的函数体。在这个例子中,函数体是空的,因为所有的初始化工作都在成员初始化列表中完成了。这是很常见的做法。
总结一下这个构造函数的行为:
- 它是一个模板构造函数,可以接受一个可调用对象 (func) 和任意数量的额外参数 (args…)。
- 它使用 explicit 关键字防止不良的隐式转换。
- 它使用转发引用 (Callable&&, Args&&…) 来接收这些参数,这样可以同时接受左值和右值。
- 它在成员初始化列表中初始化一个名为 t 的成员变量(std::thread)。
- 在初始化
t时,它使用 std::forward 将 func 和 args… 完美转发给 t 的构造函数。这意味着如果原始参数是右值(例如临时对象或通过 std::move 传递的对象),它们将被作为右值传递,允许 t 的构造函数(例如 std::thread 的构造函数)利用移动语义。如果原始参数是左值,它们将被作为左值传递。 - 这样做的目的是高效且准确地将参数传递给内部的线程对象,以启动一个新线程来执行 func(args…)。
简而言之,这个构造函数的作用是:
创建一个 joining_thread 对象,并通过完美转发传入的“可调用物”及其参数来初始化其内部的(可能是 std::thread 类型的)成员 t,从而启动一个新线程。
示例用法如下:
void my_function(int i, double d) {
std::cout << "Thread executing: " << i << ", " << d << std::endl;
}
// ...
joining_thread jt1(my_function, 10, 3.14); // func 是函数指针, args 是 10 和 3.14
int val = 20;
joining_thread jt2([](const std::string& s, int& x){
std::cout << "Lambda executing: " << s << ", " << x << std::endl;
x = 100;
}, "hello", std::ref(val)); // func 是 lambda, args 是 "hello" 和对 val 的引用包装器
struct MyFunctor {
void operator()(const std::string& msg) {
std::cout << "Functor executing: " << msg << std::endl;
}
};
MyFunctor mf;
joining_thread jt3(mf, "functor_message"); // func 是函数对象 (左值)
joining_thread jt4(MyFunctor(), "temporary_functor_message"); // func 是临时函数对象 (右值)