2014-10-02 16 views
9

Mam wiele funkcji zwrotnych z różnymi sygnaturami. Idealnie chciałbym umieścić je w wektorze i wywołać odpowiedni w zależności od pewnych warunków.Wektor std :: funkcja z różnymi sygnaturami

np.

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

Wiem, że powyższe nie jest możliwe, ale zastanawiam się, czy są jakieś alternatywy, które powinienem rozważyć. Nie udało mi się jeszcze znaleźć i eksperymentowałem ze std :: bind, ale nie udało mi się osiągnąć tego, czego chcę.

Czy coś takiego jest możliwe?

+3

Dlaczego po prostu nie dać im te same podpisy. Nie musisz używać wszystkich parametrów w funkcji –

+4

Dlaczego musisz użyć do tego wektora? Wektor może zawierać tylko typy, które mają ten sam typ (liczba polimorfizmów). Jeśli masz funkcje z różnymi podpisami, nie będziesz w stanie zrobić np. 'for (funkcja auto &&: funkcje) {funkcja(); } ', więc i tak musisz je rozdzielić, lub ujednolicić swoje podpisy (ale jeśli mają różne podpisy, ta ostatnia opcja wydaje się być brutalna - zmuszając ich do wtajemniczenia ...) – JBL

+0

@EdHeal - Myślę, że mogłem. Zastanawiam się tylko nad alternatywami. – ksl

Odpowiedz

15

Nie powiedział, co można oczekiwać, aby być w stanie zrobić z func2 po umieszczeniu go w wektorze z niewłaściwym rodzaj.

można łatwo używać std::bind, aby umieścić go w wektorze jeśli znasz argumenty wyprzedzeniem:

const std::vector<std::function<void(std::string)>> functions 
{ 
    func1, 
    std::bind(func2, std::placeholders::_1, 5, 6) 
}; 

Teraz functions[1]("foo") wezwie func2("foo", 5, 6) i przejdzie 5 i 6 do func2 za każdym razem.

Tutaj to samo przy użyciu lambda zamiast std::bind

const std::vector<std::function<void(std::string)>> functions 
{ 
    func1, 
    [=](const std::string& s){ func2(s, func2_arg1, func2_arg2); } 
}; 

Jeśli nie wiesz jeszcze argumenty, można wiązać odniesień do pewnych zmiennych:

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

Teraz functions[1]("foo") wezwie func2("foo", func2_arg1, func2_arg2) i możesz przypisać nowe wartości do liczb całkowitych, aby przekazać różne argumenty do func2.

i za pomocą funkcji lambda zamiast std::bind

const std::vector<std::function<void(std::string)>> functions 
{ 
    func1, 
    [&](const std::string& s){ func2(s, func2_arg1, func2_arg2); } 
}; 

Jest to dość brzydki choć, jak trzeba zachować int zmienne około tak długo, jak na żądanie obiektu (zamknięcia lub wyrażenia wiązania) odnosząc dla nich istnieje.

+1

Dzięki za odpowiedź. Używanie std :: bind, jak pokazałeś (jak wiem, args w czasie kompilacji) jest tym, co wypróbowałem, kiedy powiedziałem, że eksperymentowałem ze std :: bind, ale nie mogłem go uruchomić. Teraz mogę dzięki twojej odpowiedzi. – ksl

0

Jeśli masz int i string, nie możesz umieścić ich w jednym wektorze, ale możesz umieścić je w jednej strukturze lub std::tuple<>. To samo dotyczy dwóch typów funkcji.

+0

, to nie jest pytanie. – BeyelerStudios

+1

@BeyelerStudios: Wyraźnie prosi o alternatywę dla 'wektora <>, aby zachować dwie funkcje. Czy 'tuple <> nie jest w stanie tego zrobić? – MSalters

+0

przez "pewną liczbę funkcji zwrotnych" Rozumiem, że liczba jest dowolna w czasie wykonywania, krotki nie będą tu robić – BeyelerStudios

0

Jak wspomniał JBL: jak byście do nich zadzwonili, gdybyście nie znali swoich podpisów?

Pomyśl o włączeniu swoich min, max argumentów na typ parametru z jakiejś klasie bazowej Parameter, podpis zwrotna będzie void(const std::string&, const Parameter&)void(const std::string&, const Parameter*) lub w przypadku, gdy chcesz nullptr wskazać żadnych dodatkowych parametrów. Teraz twoje callbacki będą musiały sprawdzić, czy otrzymały odpowiednie parametry, jeśli takie są. Można tego dokonać za pomocą gościa, typu lub enum. Wszystkie te są plusy i minusy.

Jak zdecydujesz, do którego oddzwonić? Myślę, że powinieneś zamienić wywołania zwrotne w stylu C na obiekty obsługi, mogą one zaimplementować funkcję bool canHandle(const Parameter&), aby sprawdzić, czy procedura obsługi ma zastosowanie do przedstawionych parametrów.

Jonathan Wakely i Svalorzen przedstawiają swoje podejście, w którym parametry i funkcja są jednym i tym samym obiektem (relacja 1-do-1). W tym przykładzie są oddzielne (w przypadku gdy masz wiele do wielokrotnego relacje):

#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

Jak nazwałbyś je? Wzór użytkownika, na przykład. – MSalters

4

To, czego chcesz, jest możliwe przez polymorphism. Chodzi o to, aby stworzyć klasę z określonym podpisem, który w czasie wykonywania wywoła różne metody. Na przykład:

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

Można dokonać klas pochodnych tak skomplikowane, jak trzeba być, ale skoro w końcu wszyscy mają ten sam interfejs będzie można nazwać je wszystkie.

0

std::function powoduje wymazanie dokładnego typu obiektu funkcji, ale zachowuje podpis wywołania funkcji. Jeśli nie jesteś w stanie złożyć dodatkowych argumentów z góry, jak zaleca Jonathan Wakely, możesz użyć boost::variant< std::function<...>, std::function<...> > jako swojego członka wektorowego. Na stronie połączenia możesz sprawdzić, czy wektor zawiera odpowiedni rodzaj obiektu funkcji i wywołać go odpowiednio.

+1

Dzięki. Udało mi się uzyskać to, co chciałem, używając std :: bind po odpowiedzi Jonathana Wakely. – ksl

1

Bezpośrednia odpowiedź na twoje pytanie to "NIE". Dowolny kontener środowiska wykonawczego pozwala tylko na przechowywanie obiektów tego samego typu, a std :: function <> instancjonowane z różnymi sygnaturami będą różnymi typami danych.

Zwykle powodem może chcesz mieć „wektor funkcji z różnych podpisów” jest, gdy masz coś jak poniżej (przetwarzanie trzy krokiem gdzie interfejs wejściowy jest jednolity (interfejs wyjściowy buffer& buf i jest jednolity on_event(Event evt)), ale warstwa w środku jest niejednorodny 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); 
    ... 

w roztworze nieobejmujące mETAPROGRAMOWANIE wy mieliście zazwyczaj wstępnie gotować funktor robi end-to-end w czasie inicjalizacji i przechowywać te w wektorze:

vector <std::function<void(buffer& buf)>> handlers; 
1

Nie wiesz, jak przydatne byłoby to dla ciebie, bazuje ono na boost::any, nadmiarowe parametry są ignorowane. Możesz dodać try...catch dla , aby zapobiec awarii w przypadku niezgodności między typami argumentów i parametrów. Chociaż myślę, że regularne std::bind jest lepszym wyborem.

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