简体   繁体   English

std::thread 如何存储通过其构造函数传递的可变参数?

[英]How does std::thread store variadic arguments passed through its constructor?

Let's say I declare a thread with the following code:假设我使用以下代码声明了一个线程:

#include <thread>
#include <iostream>

void printStuff(const char* c, long x) {
   std::cout << x << " bottles of " << c << " on the wall\n";
}

int main()
{
   std::thread t(printStuff, "beer", 900000000);

   t.join();
}

How are the arguments printStuff, "beer," and 900000000 stored in the thread?参数 printStuff、“beer”和 900000000 是如何存储在线程中的?

I know they are using a variadic template, where you first pass in a function and then a parameter pack of arguments.我知道他们正在使用可变参数模板,您首先传入一个函数,然后传入一个参数包。 I am confused on how they forward all these template arguments, and then somehow call the inputted function with all the arguments when join or detach is called.我对它们如何转发所有这些模板参数感到困惑,然后在调用 join 或 detach 时以某种方式调用带有所有参数的输入函数。

std::function has similar functionality where when you call std::bind it will store a function and its arguments inside the object, and then when you call the std::function object it will just execute the bound function with its arguments. std::function 具有类似的功能,当您调用 std::bind 时,它将在对象中存储一个函数及其参数,然后当您调用 std::function 对象时,它只会执行带有参数的绑定函数。

I am basically trying to implement my own version of std::function, for my own edification.我基本上是在尝试实现我自己的 std::function 版本,以供我自己启迪。 I am curious how in C++ you would go about storing a function with a bunch of arbitrary parameters inside an object, and then having a method that would call the function with the passed in arguments.我很好奇在 C++ 中你将如何在一个对象中存储一个带有一堆任意参数的函数,然后有一个方法可以使用传入的参数调用该函数。

I have looked at both the thread and std::function class, and both seem to be using tuples in some way to store their arguments.我查看了线程和 std::function 类,两者似乎都以某种方式使用元组来存储它们的参数。 In a declaration of a tuple you have to specify what types you are storing in it:在元组的声明中,您必须指定要存储在其中的类型:

std::tuple<int, std::string> tup;

How do std::function and thread get around this by storing their variadic arguments in tuples? std::function 和 thread 如何通过将可变参数存储在元组中来解决这个问题? Furthermore, how do they retrieve the function and call it with all of the arguments?此外,他们如何检索函数并使用所有参数调用它?

I am basically trying to implement my own version of std::function, for my own edification.我基本上是在尝试实现我自己的 std::function 版本,以供我自己启迪。 I am curious how in C++ you would go about storing a function with a bunch of arbitrary parameters inside an object, and then having a method that would call the function with the passed in arguments.我很好奇在 C++ 中你将如何在一个对象中存储一个带有一堆任意参数的函数,然后有一个方法可以使用传入的参数调用该函数。

std::function is a beast of a class so I won't pretend that this is anywhere close to as complete. std::function是一流的野兽,所以我不会假装这已经接近完整。 std::function uses type erasure and small object optimization but I'll use polymorphism and store a base class pointer to a semi-anonymous implementation of a function wrapper to show how it can be done. std::function使用类型擦除和小对象优化,但我将使用多态性并将基类指针存储到函数包装器的半匿名实现中,以展示它是如何完成的。 I say semi -anonymous because it actually has a name, but it's defined locally inside the function that instantiates it.我说匿名是因为它实际上有一个名称,但它是在实例化它的函数内部本地定义的。 Storing the pointer (or the empty state) will be done in a std::unique_ptr<funcbase> .存储指针(或空状态)将在std::unique_ptr<funcbase>中完成。

The goal, as I've understood it, is to create a class with this basic interface:据我了解,目标是创建一个具有此基本接口的类:

template <class R, class... Args>
class fn_with_args<R(Args...)> {
public:
    template <class F> fn_with_args(F&& f, Args&&... args);
    R operator()();
};

That is, we need instances of fn_with_args<R(Args...)> to be able to store function pointers / functors that when invoked with the stored arguments returns R .也就是说,我们需要fn_with_args<R(Args...)>的实例来存储函数指针/函子,当使用存储的参数调用时返回R

#include <functional>
#include <memory>
#include <tuple>

template <class> class fn_with_args; // not implemented

template <class R, class... Args>
class fn_with_args<R(Args...)> {
    // an abstract base for cloneable function objects with an operator()() to call
    struct funcbase {
        virtual ~funcbase() = default;
        virtual std::unique_ptr<funcbase> clone() const = 0;
        virtual R operator()() = 0;
    };

public:
    // create empty "fn_with_args":
    fn_with_args() noexcept = default;
    fn_with_args(std::nullptr_t) noexcept {};

    // copy ctor - if store contains a pointer to a funcbase,
    //             let it clone itself
    fn_with_args(const fn_with_args& other) :
        store(other.store ? other.store->clone() : nullptr) {}

    // copy assignment
    fn_with_args& operator=(const fn_with_args& other) {
        if(this != &other) *this = fn_with_args(other); // copy+move
        return *this;
    }

    // moving can be done by default:
    fn_with_args(fn_with_args&& other) noexcept = default;
    fn_with_args& operator=(fn_with_args&& other) noexcept = default;

    // constructing and storing arguments
    template <class F>
    fn_with_args(F&& f, Args&&... args) {
        // the semi-anonymous implementation that inherits from funcbase
        // and stores both the function and the arguments:
        struct funcimpl : funcbase {
            funcimpl(F&& f, Args&&... a)
                : func{std::forward<F>(f)}, args{std::forward<Args>(a)...} {}
            
            // cloning via a base class pointer:
            std::unique_ptr<funcbase> clone() const override {
                return std::make_unique<funcimpl>(*this);
            }

            // the operator that will call `func` with the stored arguments:
            R operator()() override { return std::apply(func, args); }

            F func;                   // the actual function/functor
            std::tuple<Args...> args; // and the stored arguments
        };

        // create and store an instance of the above semi-anonymous class:
        store = std::make_unique<funcimpl>(std::forward<F>(f),
                                           std::forward<Args>(args)...);
    }

    // The call interface. It'll dereference `store` and then call it which
    // will call the overridden operator()() in the semi-anonymous `funcimpl`:
    R operator()() { 
        if(store) return (*store)();
        throw std::bad_function_call();
    }

private:
    std::unique_ptr<funcbase> store;
};

Example usage:示例用法:

#include <iostream>

double foo(int x) {
    return x * 3.14159;
}

int main() {
    fn_with_args<int(double)> f1([](double d) -> int { return d; }, 3.14159);
    std::cout << f1() << '\n';

    fn_with_args<void()> f2;  // create empty
    //f2(); // would throw "bad_function_call" since it is "empty"

    // populate it
    f2 = fn_with_args<void()>([]{ std::cout << "void\n"; });
    f2();

    // call regular function:
    fn_with_args<double(int)> f3(foo, 2);
    std::cout << f3() << '\n';

    // example with capture:
    int v = 123;
    f1 = fn_with_args<int(double)>([v](double d) -> int { return v * d; }, 3.14159);
    std::cout << f1() << '\n';

    // copying:
    auto f11 = f1;
    std::cout << f11() << '\n'; // calling the copy
}

Demo演示

you should store the params in std::tuple and invoke them using std::apply您应该将参数存储在std::tuple并使用std::apply调用它们

#include <functional>
#include <tuple>
#include <vector>

template <class R>
class Function_Wrapper {
 public:
  template <typename Callable, typename... Args>
  Function_Wrapper(Callable&& callable, Args&&... args)
      : fn_([=, args = std::make_tuple(std::forward<Args>(args)...)]() {
          return std::apply(callable, args);
        }) {}

  decltype(auto) run() {
    // call our callable with the passed in arguments
    return fn_();
  }

  decltype(auto) operator()() { return run(); }

 private:
  std::function<R()> fn_;
};

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

int main() {
  std::vector<Function_Wrapper<int>> f{{&add, 9, 30}, {&add, 1, 2}};
  return f[0].run() + f[1]();
}

Here in Compiler Explorer在编译器资源管理器中

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM