将可变函数参数转发给 std::function 对象
Forwarding variadic function parameters to a std::function object
所以我正在尝试创建一个队列来存储稍后调用的函数。为了做到这一点,我必须创建一个 std::function 对象,从传递的任何函数中放入队列。问题出在我创建这个对象并可以调用它的地方,但似乎传递的参数没有正确存储(我希望它们如何存储)
主要问题都在成员模板函数里面push(Ret (&)(...), &&...)
我尝试在 push(temp)
上方插入对临时创建的函数的调用,一切都按预期进行。但是,当我尝试从队列中访问此函数时,似乎我转发的参数已被覆盖。
class thread_queue {
public:
thread_queue() = default;
void push(std::function<void()> func) { thread_q_.push(func); }
template <typename Ret, typename ... Params>
void push(Ret (&func)(Params...), Params&&... params) {
std::function<void()> temp = [&]() { // TO DO : FIX HERE
func(std::forward<Params>(params)...);
};
push(temp);
}
void pop() { thread_q_.pop(); }
std::function<void()> front() { return thread_q_.front(); }
private:
std::queue<std::function<void()>> thread_q_;
};
void func1() {
std::cout << "Inside of func1" << std::endl;
}
void func2(int a) {
std::cout << "Inside of func2 " << a << std::endl;
}
int func3() {
std::cout << "Inside of func3" << std::endl;
return 5;
}
int func4(int a, int b) {
std::cout << "Inside of func4 " << a + b << std::endl;
return a + b;
}
int main() {
thread_queue test;
test.push(func1);
test.push(func2, 10);
test.push(func3);
test.push(func4, 1, 8);
test.front()();
test.pop();
test.front()();
test.pop();
test.front()();
test.pop();
test.front()();
test.pop();
return 0;
}
所以我想得到
Inside of func1
Inside of func2 10
Inside of func3
Inside of func4 9
但相反我得到
Inside of func1
Inside of func2 8
Inside of func3
Inside of func4 9
一些进一步的注意事项:
我想尝试转发传递的参数,所以如果我决定传递一些大对象,那么浪费的时间比复制它要少。我也考虑过以某种方式在参数上使用 shared_ptr 或 unique_ptr,但还没有对此进行测试,因为我想尽可能避免这种情况。谢谢。
编辑:看来我的问题可能与传递的右值引用有关,因为当我尝试将 10、1 和 8 转换为左值(通过将它们设置为 main 中的变量)时,它按预期工作。现在对此进行更多研究
您的队列正在保存对参数的引用,因此调用函数时参数必须仍在范围内。例如
{
int value = 1;
test.push(func2, value);
}
test.front()(); //Invalid, value is out of scope
int value2 = 2;
test.push(func2, value2);
test.front()(); //Ok, value2 is still in scope
test.push(func2, 3);
test.front()(); //Invalid, the temporary that was holding 3 is out of scope
如果您希望函数始终有效,您需要按值存储参数。在 lambda 中按值捕获参数包并不简单,但是,我们可以使用 std::bind 而不是 lambda。
#include <functional>
#include <queue>
#include <iostream>
class thread_queue {
public:
thread_queue() = default;
void push(std::function<void()> func) { thread_q_.push(func); }
template <typename Ret, typename ... Params>
void push(Ret (&func)(Params...), Params&&... params) {
std::function<void()> temp = std::bind(func, std::forward<Params>(params)...);
push(std::move(temp));
}
void pop() { thread_q_.pop(); }
std::function<void()> front() { return thread_q_.front(); } //could avoid a copy
//by returning a reference. Would be more consistent with other containers.
private:
std::queue<std::function<void()>> thread_q_;
};
void func1() {
std::cout << "Inside of func1" << std::endl;
}
void func2(int a) {
std::cout << "Inside of func2 " << a << std::endl;
}
int func3() {
std::cout << "Inside of func3" << std::endl;
return 5;
}
int func4(int a, int b) {
std::cout << "Inside of func4 " << a + b << std::endl;
return a + b;
}
int main() {
thread_queue test;
test.push(func1);
test.push(func2, 10);
test.push(func3);
test.push(func4, 1, 8);
test.front()();
test.pop();
test.front()();
test.pop();
test.front()();
test.pop();
test.front()();
test.pop();
return 0;
}
更新:
如果只有移动参数 std::bind 将无法作为对象工作,它 returns 可以被多次调用,因此无法移动存储的参数。仅移动参数的另一个问题是 std::function 要求传递给它的函数对象是可复制的。解决这些问题的一个原因是将 std::shared_ptr 存储在 std::function 中,例如
#include <functional>
#include <queue>
#include <iostream>
#include <tuple>
#include <memory>
class thread_queue {
template <typename Ret, typename... Params>
struct callable {
Ret (&func)(Params...);
std::tuple<Params...> params;
template<typename... Params2>
callable(Ret (&func1)(Params...), Params2&&... params) :
func(func1),
params{std::forward<Params2>(params)...}
{}
void operator()() {
std::apply(func, std::move(params));
}
};
public:
thread_queue() = default;
void push(std::function<void()> func) { thread_q_.push(std::move(func)); }
template <typename Ret, typename... Params>
void push(Ret (&func)(Params...), Params&&... params) {
auto data = std::make_shared<callable<Ret, Params...>>(func, std::forward<Params>(params)...);
thread_q_.push(std::function<void()>{
[data = std::move(data)]() {
(*data)();
}
});
}
void pop() { thread_q_.pop(); }
std::function<void()>& front() { return thread_q_.front(); }
private:
std::queue<std::function<void()>> thread_q_;
};
struct MoveOnly {
MoveOnly() {}
MoveOnly(MoveOnly&&) {}
};
void func5(MoveOnly m) {
std::cout << "func5\n";
}
int main() {
thread_queue test;
test.push(func5, MoveOnly{});
test.front()();
test.pop();
return 0;
}
另一个可能更快的解决方案是编写您自己的 std::function 版本。以下是这方面的一个最小示例,不包括小型缓冲区优化。
#include <functional>
#include <queue>
#include <iostream>
#include <tuple>
#include <memory>
template<class T>
class move_only_function;
template<class R, class... Args>
class move_only_function<R(Args...)>
{
struct base_callable
{
virtual R operator()(Args... args) = 0;
virtual ~base_callable() = default;
};
template<class F>
struct callable : public base_callable
{
F func;
callable(const F& f) : func(f) {}
callable(F&& f) : func(std::move(f)) {}
virtual R operator()(Args... args) override
{
return static_cast<R>(func(args...));
}
};
std::unique_ptr<base_callable> func;
public:
move_only_function(move_only_function&& other) : func(std::move(other.func)) {}
template<class F>
move_only_function(F&& f) : func(std::make_unique<callable<F>>(std::forward<F>(f))) {}
template<class... Args2>
R operator()(Args2&&... args)
{
return (*func)(std::forward<Args2>(args)...);
}
};
class thread_queue {
public:
thread_queue() = default;
void push(move_only_function<void()> func) { thread_q_.push(std::move(func)); }
template <typename Ret, typename ... Params>
void push(Ret (&func)(Params...), Params&&... params) {
thread_q_.push(move_only_function<void()>{
[func, tup=std::make_tuple(std::forward<Params>(params)...)]() mutable {
return std::apply(func, std::move(tup));
}});
}
void pop() { thread_q_.pop(); }
move_only_function<void()>& front() { return thread_q_.front(); }
private:
std::queue<move_only_function<void()>> thread_q_;
};
struct MoveOnly {
MoveOnly() {}
MoveOnly(MoveOnly&&) {}
};
void func5(MoveOnly m) {
std::cout << "func5\n";
}
int main() {
thread_queue test;
test.push(func5, MoveOnly{});
test.front()();
test.pop();
return 0;
}
所以我正在尝试创建一个队列来存储稍后调用的函数。为了做到这一点,我必须创建一个 std::function 对象,从传递的任何函数中放入队列。问题出在我创建这个对象并可以调用它的地方,但似乎传递的参数没有正确存储(我希望它们如何存储)
主要问题都在成员模板函数里面push(Ret (&)(...), &&...)
我尝试在 push(temp)
上方插入对临时创建的函数的调用,一切都按预期进行。但是,当我尝试从队列中访问此函数时,似乎我转发的参数已被覆盖。
class thread_queue {
public:
thread_queue() = default;
void push(std::function<void()> func) { thread_q_.push(func); }
template <typename Ret, typename ... Params>
void push(Ret (&func)(Params...), Params&&... params) {
std::function<void()> temp = [&]() { // TO DO : FIX HERE
func(std::forward<Params>(params)...);
};
push(temp);
}
void pop() { thread_q_.pop(); }
std::function<void()> front() { return thread_q_.front(); }
private:
std::queue<std::function<void()>> thread_q_;
};
void func1() {
std::cout << "Inside of func1" << std::endl;
}
void func2(int a) {
std::cout << "Inside of func2 " << a << std::endl;
}
int func3() {
std::cout << "Inside of func3" << std::endl;
return 5;
}
int func4(int a, int b) {
std::cout << "Inside of func4 " << a + b << std::endl;
return a + b;
}
int main() {
thread_queue test;
test.push(func1);
test.push(func2, 10);
test.push(func3);
test.push(func4, 1, 8);
test.front()();
test.pop();
test.front()();
test.pop();
test.front()();
test.pop();
test.front()();
test.pop();
return 0;
}
所以我想得到
Inside of func1
Inside of func2 10
Inside of func3
Inside of func4 9
但相反我得到
Inside of func1
Inside of func2 8
Inside of func3
Inside of func4 9
一些进一步的注意事项: 我想尝试转发传递的参数,所以如果我决定传递一些大对象,那么浪费的时间比复制它要少。我也考虑过以某种方式在参数上使用 shared_ptr 或 unique_ptr,但还没有对此进行测试,因为我想尽可能避免这种情况。谢谢。
编辑:看来我的问题可能与传递的右值引用有关,因为当我尝试将 10、1 和 8 转换为左值(通过将它们设置为 main 中的变量)时,它按预期工作。现在对此进行更多研究
您的队列正在保存对参数的引用,因此调用函数时参数必须仍在范围内。例如
{
int value = 1;
test.push(func2, value);
}
test.front()(); //Invalid, value is out of scope
int value2 = 2;
test.push(func2, value2);
test.front()(); //Ok, value2 is still in scope
test.push(func2, 3);
test.front()(); //Invalid, the temporary that was holding 3 is out of scope
如果您希望函数始终有效,您需要按值存储参数。在 lambda 中按值捕获参数包并不简单,但是,我们可以使用 std::bind 而不是 lambda。
#include <functional>
#include <queue>
#include <iostream>
class thread_queue {
public:
thread_queue() = default;
void push(std::function<void()> func) { thread_q_.push(func); }
template <typename Ret, typename ... Params>
void push(Ret (&func)(Params...), Params&&... params) {
std::function<void()> temp = std::bind(func, std::forward<Params>(params)...);
push(std::move(temp));
}
void pop() { thread_q_.pop(); }
std::function<void()> front() { return thread_q_.front(); } //could avoid a copy
//by returning a reference. Would be more consistent with other containers.
private:
std::queue<std::function<void()>> thread_q_;
};
void func1() {
std::cout << "Inside of func1" << std::endl;
}
void func2(int a) {
std::cout << "Inside of func2 " << a << std::endl;
}
int func3() {
std::cout << "Inside of func3" << std::endl;
return 5;
}
int func4(int a, int b) {
std::cout << "Inside of func4 " << a + b << std::endl;
return a + b;
}
int main() {
thread_queue test;
test.push(func1);
test.push(func2, 10);
test.push(func3);
test.push(func4, 1, 8);
test.front()();
test.pop();
test.front()();
test.pop();
test.front()();
test.pop();
test.front()();
test.pop();
return 0;
}
更新: 如果只有移动参数 std::bind 将无法作为对象工作,它 returns 可以被多次调用,因此无法移动存储的参数。仅移动参数的另一个问题是 std::function 要求传递给它的函数对象是可复制的。解决这些问题的一个原因是将 std::shared_ptr 存储在 std::function 中,例如
#include <functional>
#include <queue>
#include <iostream>
#include <tuple>
#include <memory>
class thread_queue {
template <typename Ret, typename... Params>
struct callable {
Ret (&func)(Params...);
std::tuple<Params...> params;
template<typename... Params2>
callable(Ret (&func1)(Params...), Params2&&... params) :
func(func1),
params{std::forward<Params2>(params)...}
{}
void operator()() {
std::apply(func, std::move(params));
}
};
public:
thread_queue() = default;
void push(std::function<void()> func) { thread_q_.push(std::move(func)); }
template <typename Ret, typename... Params>
void push(Ret (&func)(Params...), Params&&... params) {
auto data = std::make_shared<callable<Ret, Params...>>(func, std::forward<Params>(params)...);
thread_q_.push(std::function<void()>{
[data = std::move(data)]() {
(*data)();
}
});
}
void pop() { thread_q_.pop(); }
std::function<void()>& front() { return thread_q_.front(); }
private:
std::queue<std::function<void()>> thread_q_;
};
struct MoveOnly {
MoveOnly() {}
MoveOnly(MoveOnly&&) {}
};
void func5(MoveOnly m) {
std::cout << "func5\n";
}
int main() {
thread_queue test;
test.push(func5, MoveOnly{});
test.front()();
test.pop();
return 0;
}
另一个可能更快的解决方案是编写您自己的 std::function 版本。以下是这方面的一个最小示例,不包括小型缓冲区优化。
#include <functional>
#include <queue>
#include <iostream>
#include <tuple>
#include <memory>
template<class T>
class move_only_function;
template<class R, class... Args>
class move_only_function<R(Args...)>
{
struct base_callable
{
virtual R operator()(Args... args) = 0;
virtual ~base_callable() = default;
};
template<class F>
struct callable : public base_callable
{
F func;
callable(const F& f) : func(f) {}
callable(F&& f) : func(std::move(f)) {}
virtual R operator()(Args... args) override
{
return static_cast<R>(func(args...));
}
};
std::unique_ptr<base_callable> func;
public:
move_only_function(move_only_function&& other) : func(std::move(other.func)) {}
template<class F>
move_only_function(F&& f) : func(std::make_unique<callable<F>>(std::forward<F>(f))) {}
template<class... Args2>
R operator()(Args2&&... args)
{
return (*func)(std::forward<Args2>(args)...);
}
};
class thread_queue {
public:
thread_queue() = default;
void push(move_only_function<void()> func) { thread_q_.push(std::move(func)); }
template <typename Ret, typename ... Params>
void push(Ret (&func)(Params...), Params&&... params) {
thread_q_.push(move_only_function<void()>{
[func, tup=std::make_tuple(std::forward<Params>(params)...)]() mutable {
return std::apply(func, std::move(tup));
}});
}
void pop() { thread_q_.pop(); }
move_only_function<void()>& front() { return thread_q_.front(); }
private:
std::queue<move_only_function<void()>> thread_q_;
};
struct MoveOnly {
MoveOnly() {}
MoveOnly(MoveOnly&&) {}
};
void func5(MoveOnly m) {
std::cout << "func5\n";
}
int main() {
thread_queue test;
test.push(func5, MoveOnly{});
test.front()();
test.pop();
return 0;
}