C++ 可变参数模板

黎 浩然/ 9 12 月, 2023/ C/C++, 计算机/COMPUTER/ 0 comments

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. {}

  • 这是构造函数的函数体。在这个例子中,函数体是空的,因为所有的初始化工作都在成员初始化列表中完成了。这是很常见的做法。

总结一下这个构造函数的行为:

  1. 它是一个模板构造函数,可以接受一个可调用对象 (func) 和任意数量的额外参数 (args…)。
  2. 它使用 explicit 关键字防止不良的隐式转换。
  3. 它使用转发引用 (Callable&&, Args&&…) 来接收这些参数,这样可以同时接受左值和右值。
  4. 它在成员初始化列表中初始化一个名为 t 的成员变量(std::thread)。
  5. 在初始化 t 时,它使用 std::forward 将 func 和 args… 完美转发给 t 的构造函数。这意味着如果原始参数是右值(例如临时对象或通过 std::move 传递的对象),它们将被作为右值传递,允许 t 的构造函数(例如 std::thread 的构造函数)利用移动语义。如果原始参数是左值,它们将被作为左值传递。
  6. 这样做的目的是高效且准确地将参数传递给内部的线程对象,以启动一个新线程来执行 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 是临时函数对象 (右值)

Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*