矢量std ::功能与不同的签名

时间:2014-10-02 10:00:33

标签: c++ c++11 stdvector std-function

我有许多具有不同签名的回调函数。理想情况下,我想将它们放在一个向量中,并根据某些条件调用适当的一个。

e.g。

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但没有成功实现我想要的目标。

这样的事情可能吗?

9 个答案:

答案 0 :(得分:18)

在将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),并且每次都会将56传递给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个变量。

答案 1 :(得分:6)

通过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;
}

您可以根据需要使派生类变得复杂,但最终它们都具有相同的界面,您可以调用所有这些类。

答案 2 :(得分:2)

对于C++ 17std::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");
  }
}

答案 3 :(得分:1)

直接回答你的问题是&#34;否&#34;。任何运行时容器只允许您存储相同类型的对象和std :: function&lt;&gt;使用不同签名进行实例化将是不同的数据类型。

一般来说,你可能希望拥有&#34;具有不同签名的函数向量&#34;是你有类似下面的东西(输入界面统一的三步处理(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;

答案 4 :(得分:1)

不确定这对您有用,它基于boost::any,忽略冗余参数。您可以为try...catch添加boost::bad_any_cast,以防止在参数'和参数'类型不匹配的情况下发生崩溃。虽然我认为常规std::bind是更好的选择。

DEMO

#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);
    }
}

答案 5 :(得分:0)

如果你有一个int和一个字符串,你不能将它们放在一个向量中,但你可以将它们放在一个结构或std::tuple<>中。这同样适用于两种函数类型。

答案 6 :(得分:0)

正如JBL所说:如果你不知道他们的签名,你会怎么称呼他们?

考虑将min, max参数转换为具有基类Parameter的参数类型,如果您希望{{1},则回调签名将为void(const std::string&, const Parameter&)void(const std::string&, const Parameter*)表示没有其他参数。现在你的回调需要检查是否给出了正确的参数(如果有的话)。这可以通过使用visitor,typeid或enum来完成。所有这些都有利有弊。

您将如何决定拨打哪个回叫?我认为您应该将C样式的回调转换为处理程序对象,它们可能会实现一个函数nullptr来测试处理程序是否适用于所显示的参数。

Jonathan Wakely和Svalorzen介绍了他们的方法,其中参数和函数是同一个对象(1对1的关系)。在这个例子中,它们是分开的(如果你有多对多的关系):

bool canHandle(const Parameter&)

答案 7 :(得分:0)

std::function删除函数对象的确切类型,但保留函数调用签名。如果你不能像Jonathan Wakely建议的那样提前bind额外的论点,你可以使用boost::variant< std::function<...>, std::function<...> >作为你的向量成员。在调用站点,您可以检查向量是否包含正确类型的函数对象并相应地调用它。

答案 8 :(得分:0)

我尝试使用函数指针,将std::function*强制转换为void*,可以编译成功,但有时会导致分段错误:

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;
}