2016-06-10 21 views
5

Sprawa próbuję wdrożyć tutaj jest klasa bazowa, która ma funkcję (nazwijmy to modify_command), które mogą przyjmować wiele różnych rodzajów praktycznie tak Klasy pochodne mogą realizować funkcję modify_command jak uważają za stosowne. Teraz mam coś wzdłuż tych linii w klasie bazowej:Projektowanie w C++: Przeciążanie/Nadpisywanie wielu funkcji, czyszczenie?

class Base 
{ 
    template<typename Command> 
    void modify_command(Command cmd) 
    { 
     std::cout << "Modify command called with unimplemented command type:" << typeid(cmd).name(); 
    } 

    virtual void modify_command(SpecificCommandA cmd) 
    { 
     modify_command<SpecificCommandA>(cmd); // Calls the templated function 
    } 

    virtual void modify_command(SpecificCommandB cmd) 
    { 
     modify_command<SpecificCommandB>(cmd); // Calls the templated function 
    } 

    // etc. 
}; 

Następnie w klasie pochodnej:

class Derived : public Base 
{ 
    virtual void modify_command(SpecificCommandA cmd) 
    { 
     cmd.x = 1; 
     cmd.y = 2; 
    } 
} 

Oczywiście funkcja wirtualnego szablon nie jest możliwe więc w jakiejś formie jestem będzie musiała wymieniać deklaracje funkcji dla wielu różnych argumentów, co z pewnością zakłóci definicję klasy i może utrudnić (z czasem) dodatkowe typy poleceń, które mają być obsługiwane.

Celem posiadania templatu Funkcja ed jest w tym przypadku kompilacji bez potrzeby definicji modify_command (SpecificCommandC) do logowania błąd:

Base * base = new Derived(); 
SpecificCommandA a; 
SpecificCommandB b; 
SpecificCommandC c; 
base->modify_command(a); //Set's x and y 
base->modify_command(b); //Outputs that command type is unimplemented 
base->modify_command(c); //Outputs that command type is unimplemented 

Nienawidzę jak mam to działa, czy ktoś ma sugestie, w jaki sposób może to być czyszczone/zreimplementowany? Liczba poleceń będzie rosła wraz z dojrzewaniem oprogramowania, więc niezbędna jest rozszerzalność.

Edit: Gramatyka

+0

Utwórz klasę bazową SpecificCommand z funkcją składową, np. onModifyCommand, która wywołuje modify_command. – kfsone

+0

@kfsone Nie jestem pewien, czy rozumiem co masz na myśli. Każda z moich klas pochodnych zmodyfikuje komendę inaczej (czasem wcale). W jaki sposób dałoby to moim Derywacjom możliwość określenia własnej implementacji do modyfikacji polecenia? – mascoj

+0

Wygląda na to, że potrzebujesz odpowiednika funkcji wirtualnego szablonu bez konieczności deklarowania wszystkich konkretnych przeciążeń w klasach pochodnych jako funkcji wirtualnych w klasie bazowej. Proszę potwierdzić – EdMaster

Odpowiedz

1

Niestety, co rozwiązałoby problem jest wirtualna metoda szablon, że nie jest to możliwe.

Oto bardziej C-owski rozwiązanie przedstawia C++ świata, które można obejść ograniczenia:

#include<unordered_map> 
#include<functional> 
#include<memory> 
#include<iostream> 
#include<utility> 

struct BaseCommand { 
    static int counter; 
}; 

int BaseCommand::counter = 0; 

template<class T> 
struct Command: BaseCommand { 
    static int type() { 
     static const int t = ++counter; 
     return t; 
    } 
}; 

struct SpecificCommand1: Command<SpecificCommand1> {}; 
struct SpecificCommand2: Command<SpecificCommand2> {}; 

class Base { 
    struct Handler { 
     virtual void operator()(BaseCommand &cmd) = 0; 
    }; 

    template<typename T> 
    struct THandler: Handler { 
     std::function<void(T)> func; 
     void operator()(BaseCommand &cmd) override { 
      func(static_cast<T&>(cmd)); 
     } 
    }; 
protected: 
    template<typename T> 
    void assign(std::function<void(T)> f) { 
     auto handler = std::make_unique<THandler<T>>(); 
     handler->func = f; 
     handlers[T::type()] = std::move(handler); 
    } 

public: 
    template<typename Command> 
    void modifyCommand(Command cmd) { 
     auto it = handlers.find(Command::type()); 
     if(it == handlers.end()) { 
      std::cout << "Modify command called with unimplemented command type: " << Command::type(); 
     } else { 
      auto &h = *(it->second); 
      h(cmd); 
     } 
    } 

private: 
    std::unordered_map<int, std::unique_ptr<Handler>> handlers; 
}; 

class Derived: public Base { 
public: 
    Derived() { 
     std::function<void(SpecificCommand1)> f = 
      [](SpecificCommand1) { 
       std::cout << "handler for SpecificCommand1" << std::endl; 
      }; 

     assign(f); 
    } 
}; 

int main() { 
    Base *b = new Derived; 
    b->modifyCommand(SpecificCommand1{}); 
    b->modifyCommand(SpecificCommand2{}); 
} 

Podstawowym założeniem jest to, aby dać typ liczbowy w czasie wykonywania na polecenia (jest możliwe zrobić z idiomem CRTP - patrz BaseCommand i klasa szablonu Command).
Ta wartość jest dostępna, to kwestia utworzenia wymazanej czcionką obsługi do obsługi poleceń, dla których chcesz zapewnić określoną implementację (patrz assign i Handler/THandler).
Po prawidłowym ustawieniu wszystkich elementów wystarczy zaprojektować i zainicjować te procedury obsługi w swoich klasach pochodnych. Można to zrobić za pomocą std::function, można użyć jako procedury obsługi lambda, metoda publiczna lub prywatna, metoda statyczna i tak dalej.
Aby uzyskać więcej informacji, patrz konstruktor obiektu Derived.

+0

Dzięki za to. Myślę, że mapa handlerów może być najlepszym sposobem na zrobienie tego z ponad 30 deklaracjami, +1 – mascoj

+0

Postanowiłem wziąć twój przykład i trochę to zepsuć, żeby lepiej dopasować się do moich potrzeb, było kilka informacji, które pominąłem w pytaniu, które to własność, która z łatwością wytwarza klucze do tego użycia. Dzięki jeszcze raz! – mascoj

+0

@mascoj Nie ma za co. :-) – skypjack

1

Nie jestem pewien, czy to jest to, co chcesz, ale idę do przodu z tym, co mam wymyślić:

Mała metaprogram znaleźć typ w typelist/lista parametrów variadic. Potrzebny do zidentyfikowania, czy polecenie jest częścią implementacji czy nie.

namespace meta { 
template <typename... T> struct list{}; 
template <typename F, typename T> struct has_type; 

template <typename F> 
struct has_type<F, list<>> { 
    using type = typename std::false_type; 
    static constexpr bool value = false; 
}; 

template <typename F, typename... T> 
struct has_type<F, list<F, T...>> { 
    using type = typename std::true_type; 
    static constexpr bool value = true; 
}; 

template <typename F, typename H, typename... T> 
struct has_type<F, list<H,T...>> { 
    using type = typename std::false_type; 
    static constexpr bool value = 
    std::is_same<F, typename std::decay<H>::type>::value ? true : has_type<F, list<T...>>::value; 
}; 
} 

Teraz Określ swoje polecenia mające wspólną klasę bazową:

struct CommandBase {}; 

struct CommandA: CommandBase {}; 
struct CommandB: CommandBase {}; 
struct SomeCommandType: CommandBase {}; 
struct CommandC: CommandBase {}; 

using AvailableCommmands = meta::list<CommandA, CommandB, SomeCommandType>; 

Typ AvailableCommmands decyduje, jaki rodzaj powinny pochodzić not implemented wiadomość. Jeśli konkretny typ koomandu nie występuje na meta :: list, to dla tego typu należy wydrukować komunikat AvailableCommmands.

Reszta kodu (podstawa + pochodne):

class Base 
{ 
public: 
    template <typename T> 
    void on_modify_command(T cmd) { 
    do_on_modify_command(cmd, typename meta::has_type<T, AvailableCommmands>::type()); 
    } 
private: 
    virtual void do_on_modify_command(CommandBase&, std::true_type) = 0; 
    virtual void do_on_modify_command(CommandBase& b, std::false_type) { 
    std::cout << "Not implemented" << std::endl; 
    } 

}; 

class Derived: public Base 
{ 
public: 
    void do_on_modify_command(CommandBase& cmd, std::true_type) { 
    std::cout << "Specialized command implementation" << std::endl; 
    impl(*static_cast<SomeCommandType*>(&cmd)); 
    } 
    void impl(SomeCommandType cmd) { 
    std::cout << "huh!!" << std::endl; 
    } 

}; 

int main() { 
    CommandA ca; 
    Base* b = new Derived; 
    b->on_modify_command(ca); 
    CommandC cc; 
    b->on_modify_command(cc); 

    return 0; 
} 

Kod jest zrozumiałe niechlujny, jestem pewien, że musi być jakiś lepszy sposób to zrobić.

+0

Interesujący sposób rozwiązania tego problemu. Może nie pasować do tego, czego szukam. Próbuję oddzielić implementacje w taki sposób, że jedna klasa pochodna może zawierać program obsługi CommandA, ale nie CommandB, a inna klasa może zawierać program obsługi CommandB, a nie CommandA, a inny może obsługiwać A i B. Przeanalizowałem tylko to, ale wydaje się, że to rozwiązanie wiąże go z tym, że wszystkie wyprowadzenia muszą obsługiwać polecenia z listy? – mascoj

+0

@mascoj: Prawdopodobnie można to zrobić za pomocą 'dynamic_cast' zamiast' static_cast' wewnątrz funkcji 'do_on_modify_command'. Aby to zadziałało, będziesz musiał dokonać polimorfizmu 'CommandBase' przez dodanie co najmniej jednej funkcji wirtualnej. – Arunmu

0

I może nie rozumieć swoje pytanie dokładnie, ale przyszedł z rozwiązania kwestii, w jaki sposób, aby umożliwić przeciążenia w klasach pochodnych, które będą działać jako „funkcje wirtualnych” na szablonie. zrobiłem małą zmianę: SpecificCommandA i SpecificCommandB mają wspólnego przodka Command klasę, jeśli w kodzie są nie wystarczy utworzyć klasę Command i mieć coś podobnego struct SpecificAPrime: Common, SpecificCommandA {};

najpierw kodu:

#include <typeindex> 
#include <unordered_map> 

struct Command { virtual ~Command() {}; }; 

struct SpecificCommandA: Command {}; 
struct SpecificCommandB: Command {}; 
struct SpecificCommandC: Command {}; 


struct Base { 
    virtual ~Base() {}; 

    virtual void modify_command(Command &c, std::type_index); // default 

    void modify_command(Command &c) { 
     modify_command(c, std::type_index(typeid(c))); 
    } 
}; 

template<typename Der, typename SpecC> struct Modifier { 
    static void execute(Der &d, Command &c) { 
     d.modify_command(static_cast<SpecC &>(c)); 
    } 
}; 

struct Derived: Base { 
    using Base::modify_command; // <- Because there are overloads 
    virtual void modify_command(SpecificCommandB &); 
     // Put here the specific code you want for SpecificCommandB. 
     // Note that this can be overriden in derived classes 

    void modify_command(Command &c, std::type_index ti) override { 
     using callable = void (*)(Derived &, Command &); 
     const static std::unordered_map<std::type_index, callable> derived_map = 
     { 
      { std::type_index(typeid(SpecificCommandB)), Modifier<Derived, SpecificCommandB>::execute } 
     }; 
     auto find_result = derived_map.find(ti); 
     if(derived_map.end() == find_result) { Base::modify_command(c, ti); } 
     else { (*find_result->second)(*this, c); } 
    } 
}; 

idea jest następująca: ponieważ teraz Polecenia mają wspólnego przodka i są polimorficzne, polimorfizm dynamiczny może być stosowany zamiast szablonów, w tym operatora typeid.

Teraz modify_command(Command &) jest nie szablon, ani polimorficzny funkcja, co robi jest znaleźć type_index polecenia i przekazuje go do funkcji polimorficznych. Implementacje funkcji polimorficznych mają wewnętrzną mapę indeksu typu, którą umieją modyfikować. Jeśli podanej type_index nie ma na mapie, domyślnie jest to implementacja klasy podstawowej, a jeśli na mapie znajduje się type_index, wywołaj zmapowany element, który jest po prostu utworzeniem funkcji szablonu, która wywołuje określone przeciążenie.

W pewnym sensie to, co to robi, jest polimorficznym przełącznikiem typu.

Oto kompletny, działa przykład: http://ideone.com/K1Xmdk