2014-10-02 40 views
9

我有許多具有不同簽名的回調函數。理想情況下,我希望將這些放入矢量中,並根據特定條件調用適當的函數。具有不同簽名的std :: function的向量

例如

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,但沒有設法達到我想要的。

這樣的事情可能嗎?

+3

爲什麼不給他們同樣的簽名。您不必使用功能中的所有參數 – 2014-10-02 10:02:37

+4

爲什麼您需要爲此使用矢量?一個向量只能包含共享相同類型的類型(多態性計數)。如果您具有不同簽名的功能,您將無法執行(auto && function:functions){function(); }',所以你需要保持它們分開,或者統一它們的簽名(但是如果它們具有不同的簽名,這最後一個選項感覺就像強迫它們進入向量......) – JBL 2014-10-02 10:07:55

+0

@EdHeal - 我想我可以。只是想知道替代品。 – ksl 2014-10-02 10:12:37

回答

15

你還沒說你希望能夠把它在一個向量與錯誤後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),並且每次都會通過56func2

下面是一個使用拉姆達代替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

感謝您的回覆。使用std :: bind你所顯示的方式(因爲我知道編譯時的參數)是我曾經嘗試使用std :: bind但不能使其工作時所嘗試的。我現在可以感謝你的迴應。 – ksl 2014-10-02 14:46:11

0

如果你有一個int和一個字符串,你不能把它們放在一個向量中,但是你可以把它們放在一個結構體中或std::tuple<>。這同樣適用於兩種功能類型。

+0

這不是問題 – BeyelerStudios 2014-10-02 10:15:52

+1

@BeyelerStudios:他明確要求'vector <>'的另一種方法來保存這兩個函數。是不是'tuple <>'能夠做到這一點? – MSalters 2014-10-02 10:17:12

+0

by「一些回調函數」我知道這個數字在運行時是任意的,這裏的元組不會在這裏做 – BeyelerStudios 2014-10-02 10:22:05

0

正如JBL提到的:如果你不知道他們的簽名,你會怎樣稱呼他們?

想想把你的min, max參數到的參數類型與一些基類Parameter,回調簽名會的情況下,void(const std::string&, const Parameter&)void(const std::string&, const Parameter*)你想nullptr表示沒有額外的參數。現在你的回調將需要檢查是否給了正確的參數。這可以通過使用訪問者,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 
     } 
} 
+0

你會怎麼稱呼他們?訪客模式,例如。 – MSalters 2014-10-02 10:18:33

4

你想要的是可以通過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; 
} 

你可以那樣複雜,因爲你需要爲派生類,但因爲最終他們都具有相同的接口,你就可以打電話給他們。

0

std::function擦除函數對象的確切類型,但保留函數調用簽名。如果您不能按照Jonathan Wakely的建議事先提供額外參數bind,則可以使用boost::variant< std::function<...>, std::function<...> >作爲您的向量成員。然後在呼叫站點檢查向量是否包含正確的函數對象並相應地調用它。

+1

Thanks.I按照Jonathan Wakely的回覆,我能夠使用std :: bind得到我想要的工作。 – ksl 2014-10-02 14:48:15

1

直接回答你的問題是「否」。任何運行時容器只會讓你存儲相同類型的對象,並且用不同的簽名實例化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; 
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); 
    } 
} 
相關問題