具有不同签名的std::函数的矢量
Vector of std::function with different signatures
我有许多具有不同签名的回调函数。理想情况下,我想把这些放在一个向量中,并根据某些条件调用合适的向量。
例如
void func1(const std::string& value);
void func2(const std::string& value, int min, int max);
const std::vector<std::function<void(std::string)>> functions
{
func1,
func2,
};
我意识到以上是不可能的,但我想知道是否有其他选择我应该考虑。我还没能找到,我已经尝试过std::bind
,但没能实现我想要的。
这样的事情可能吗?
您还没有说明在将func2
放入错误类型的向量后,您希望能够对其执行什么操作。
如果您提前知道参数,您可以很容易地使用std::bind
将其放入向量中:
const std::vector<std::function<void(std::string)>> functions
{
func1,
std::bind(func2, std::placeholders::_1, 5, 6)
};
现在functions[1]("foo")
将调用func2("foo", 5, 6)
,并且每次都会将5
和6
传递给func2
。
使用lambda而不是std::bind
也是一样的
const std::vector<std::function<void(std::string)>> functions
{
func1,
[=](const std::string& s){ func2(s, func2_arg1, func2_arg2); }
};
如果您还不知道参数,可以将引用绑定到一些变量:
int func2_arg1 = 5;
int func2_arg2 = 6;
const std::vector<std::function<void(std::string)>> functions
{
func1,
std::bind(func2, std::placeholders::_1, std::ref(func2_arg1), std::ref(func2_arg2))
};
现在functions[1]("foo")
将调用func2("foo", func2_arg1, func2_arg2)
,您可以为整数分配新值,以将不同的参数传递给func2
。
使用lambda函数代替std::bind
const std::vector<std::function<void(std::string)>> functions
{
func1,
[&](const std::string& s){ func2(s, func2_arg1, func2_arg2); }
};
不过,这非常难看,因为只要引用int
变量的可调用对象(闭包或绑定表达式)存在,就需要保留这些变量。
通过polymorphism
可以实现您想要的。其想法是创建一个具有特定签名的类,该类在运行时将调用不同的方法。例如:
#include <iostream>
#include <functional>
#include <memory>
#include <vector>
void foo(int) {
std::cout << "I'm foo!n";
}
int bar(char, double) {
std::cout << "I'm bar!n";
}
class MyFunction {
public:
virtual ~MyFunction(){}
virtual void operator()() = 0;
};
class MyFunctionA : public MyFunction {
public:
virtual void operator()() {
foo(4);
}
};
class MyFunctionB : public MyFunction {
public:
MyFunctionB(std::function<int(char,double)> f, char arg1, double arg2) : fun_(f), arg1_(arg1), arg2_(arg2) {}
virtual void operator()() {
fun_(arg1_, arg2_);
}
private:
std::function<int(char,double)> fun_;
char arg1_;
double arg2_;
};
int main() {
using MyFunPtr = std::unique_ptr<MyFunction>;
std::vector<MyFunPtr> v;
v.emplace_back(new MyFunctionA());
v.emplace_back(new MyFunctionB(bar, 'c', 3.4));
for ( auto&& myfun : v ) {
(*myfun)();
}
return 0;
}
您可以根据需要使派生类变得复杂,但由于最终它们都具有相同的接口,因此您可以调用所有派生类。
对于C++ 17
,std::variant可用于保存具有不同签名的std::function
。在这种情况下,函数std::holds_alternative允许您在运行时区分它们:
样品:
#include <variant>
#include <iostream>
#include <functional>
#include <vector>
using FooInt = std::function<void(int)>;
using FooStr = std::function<void(std::string)>;
using FooVariant = std::variant<FooInt, FooStr>;
void foo(int a){
std::cout << a << std::endl;
}
void bar(std::string a){
std::cout << a << std::endl;
}
int main()
{
std::vector<FooVariant> v;
v.push_back(foo);
v.push_back(bar);
for(auto& f: v){
if (std::holds_alternative<FooInt>(f))
std::get<FooInt>(f)(1);
else if (std::holds_alternative<FooStr>(f))
std::get<FooStr>(f)("hello");
}
}
问题的直接答案是"否"。任何运行时容器都只允许存储相同类型和std::function<>的对象用不同签名实例化的将是不同的数据类型。
通常,您可能想要"具有不同签名的函数向量"的原因是,当您具有以下内容时(输入接口统一(buffer& buf
,输出接口统一on_event(Event evt)
)的三步处理,但中间的层是异构process_...(...)
receive_message(buffer& buf)
switch(msg_type(buf))
case A:
case B:
...
process_A(A& a, One x, Two y)
...
dispatch(Event evt);
...
process_B(B& b, Three x);
...
dispatch(Event evt);
...
在一个不涉及元编程的解决方案中,你通常会在初始化时预先编写一个函数来完成端到端的操作,并将其存储在向量中:
vector <std::function<void(buffer& buf)>> handlers;
如果你有一个int和一个字符串,你不能把它们放在一个向量中,但你可以把它们放进一个结构或std::tuple<>
中。这同样适用于两种函数类型。
std::function
擦除函数对象的确切类型,但保留函数调用签名。如果不能像Jonathan Wakely建议的那样提前bind
额外的参数,可以使用boost::variant< std::function<...>, std::function<...> >
作为向量成员。在调用站点,您可以检查向量是否包含正确类型的函数对象,并相应地调用它。
不确定这对您有多有用,它基于boost::any
,冗余参数被忽略。您可以为boost::bad_any_cast
添加try...catch
,以防止在参数和参数类型不匹配的情况下崩溃。尽管我认为常规std::bind
是一个更好的选择。
演示
#include <boost/any.hpp>
#include <functional>
#include <vector>
#include <cstddef>
#include <memory>
#include <tuple>
#include <utility>
#include <iostream>
#include <string>
struct IGenericFunction
{
virtual ~IGenericFunction() = default;
virtual void call(boost::any a1 = boost::any{}
, boost::any a2 = boost::any{}
, boost::any a3 = boost::any{}
, boost::any a4 = boost::any{}) = 0;
};
template <typename... Args>
class GenericFunction : public IGenericFunction
{
public:
GenericFunction(std::function<void(Args...)> f) : _f{ f } {}
virtual void call(boost::any a1 = boost::any{}
, boost::any a2 = boost::any{}
, boost::any a3 = boost::any{}
, boost::any a4 = boost::any{}) override
{
call_func(std::make_tuple(a1, a2, a3, a4)
, std::make_index_sequence<sizeof...(Args)>{});
}
private:
template <typename Tuple, std::size_t... Indices>
void call_func(Tuple t, std::index_sequence<Indices...> s)
{
_f(boost::any_cast<
typename std::tuple_element<Indices, Params>::type
>(std::get<Indices>(t))...);
}
std::function<void(Args...)> _f;
using Params = std::tuple<Args...>;
};
template <typename... Args>
std::shared_ptr<IGenericFunction> make_generic_function_ptr(void(*f)(Args...))
{
return std::make_shared<GenericFunction<Args...>>(f);
}
void func1(const std::string& value)
{
std::cout << "func1 " << value << std::endl;
}
void func2(const std::string& value, int min, int max)
{
std::cout << "func2 " << value << " " << min << " " << max << std::endl;
}
int main()
{
std::vector<std::shared_ptr<IGenericFunction>> functions;
functions.push_back(make_generic_function_ptr(&func1));
functions.push_back(make_generic_function_ptr(&func2));
for (auto f : functions)
{
f->call(std::string("abc"), 1, 2);
}
}
正如JBL所提到的:如果你不知道他们的签名,你会怎么称呼他们?
考虑将min, max
参数转换为具有某个基类Parameter
的参数类型,如果您希望nullptr
不指示其他参数,则回调签名将为void(const std::string&, const Parameter&)
或void(const std::string&, const Parameter*)
。现在,您的回调将需要检查是否为它们提供了正确的参数(如果有的话)。这可以通过使用访问者、typeid或枚举来完成。所有这些都有利弊。
您将如何决定呼叫哪个回叫?我认为您应该将C风格的回调转换为处理程序对象,它们可能会实现一个函数bool canHandle(const Parameter&)
来测试处理程序是否适用于所提供的参数。
Jonathan Wakely和Svalorzen提出了他们的方法,其中参数和函数是同一对象(1对1关系)。在这个例子中,它们是分开的(如果你有多对多的关系):
#include <cassert>
#include <string>
#include <typeinfo>
#include <vector>
class ParameterBase {
public:
ParameterBase(const std::string& value) : m_value(value) { }
virtual ~ParameterBase() { }
const std::string& GetValue() const { return m_value; }
private:
std::string m_value;
};
class HandlerBase {
public:
virtual bool CanHandle(const ParameterBase& params) const = 0;
virtual void Handle(const ParameterBase& params) = 0;
};
class Handler1 : public HandlerBase {
public:
class Parameter : public ParameterBase {
public:
Parameter(const std::string& value) : ParameterBase(value) { }
~Parameter() { }
};
bool CanHandle(const ParameterBase& params) const { return typeid(Parameter) == typeid(params); }
void Handle(const ParameterBase& params) {
assert(CanHandle(params));
const Parameter& p = static_cast<const Parameter&>(params);
// implement callback1
}
};
void foo(const std::vector<HandlerBase*>& handlers) {
Handler1::Parameter params("value");
for(auto handler : handlers)
if(handler->CanHandle(params)) {
handler->Handle(params);
// no break: all handlers may handle params
// with break: only first handler (if any) handles params
}
}
我尝试使用函数指针,并强制执行std::function<int(int)>*要作废*,可以成功编译,但有时会导致分段错误:
int Fun(int a)
{
std::cout << a << std::endl;
return ++(++a);
}
int main()
{
std::function<int(int)> originPFun = Fun;
void *ppFun;
// ppFun = (void*)&Fun; // This way will cause segmentation fault
ppFun = (void*)&originPFun; // This way can run seuccessful and get right result
std::function<int(int)> *pFun = (std::function<int(int)>*)(ppFun);
std::cout << (*pFun)(5) << std::endl;
system("pause");
return 0;
}
- 使用std::函数映射对象方法
- 可组合的lambda/std::函数与std::可选
- 当使用透明的std函数对象时,我们还需要写空的尖括号吗
- std::函数常量正确性未遵循
- 具有变量Number of Arguments的std::函数的矢量
- 从类型std::函数传递变量失败,尽管调用方期望的类型完全相同
- 如何调用存储在指向"std::函数"的指针中的 lambda?
- C++ STD 函数运算符:有没有一种方法可以通过函数将一个向量映射到另一个向量上?
- 从具有按值捕获的 lambda 移动构造 std::函数时,移动构造函数调用两次
- 如何将类 1 的 std::函数绑定到类 2 的函数?
- 如何制作可以接受任何类型的参数的 std::函数和 lambda
- 如何将 STL 队列推送函数绑定到 std::函数?
- std::函数不起作用,但普通的旧函数指针可以 - 为什么?
- 获取 std::函数以推断按引用传递/按值传递
- std::bind 和 std::函数术语不值为接受 0 个参数?
- 从其存储的回调中删除 std::函数是否安全
- 创建 std::函数,它返回具有函数成员值的变量.分段错误
- 创建一个带有 lambda 的 std::函数,而不知道函数的参数
- std::函数的解释
- 在调用过程中删除 std::函数