2016-04-10 33 views
2

Angenommen, ich ein Objekt, das von anderen Objekten zu beobachten ist:Mit C++ variadischen Vorlagen, wie kann ich eine Gruppe von heterogen typisierten Objekten speichern UND über sie iterieren?

struct Object 
{ 
    struct Listener 
    { 
     virtual void fire() = 0; 
    } 

    Object(std::vector<Listener *> &listeners) : 
     listeners_(listeners) 
    {} 

    void fire() 
    { 
     for(auto *l : listeners_) 
      l->fire(); 
    } 

private: 
    std::vector<Listener *> listeners_; 
}; 

Nun würde ich die gleiche Sache mit Vorlagen zu tun. Hier ist ein Skelett, was ich meine:

template<typename ... Listeners> 
struct Object 
{ 
    Object(Listeners&&...listeners) 
    { 
     // How do I store each of the differently-typed references? 
    } 

    void fire() 
    { 
     // How do I iterate over the list of listeners? 
    } 
}; 

Beachten Sie, dass der Schlüssel Sache hier ist, dass ich virtuelle Funktionsaufrufe zu vermeiden, bin versucht. Ich möchte nicht, dass meine Listener (im Template-Code) eine reine virtuelle Klasse oder ähnliches ableiten müssen.

+1

Wäre das nicht jedes Paar 'Object <> 'sehr eng dazu' Hörer'? Sind Sie sicher, dass dies wirklich ein weises Design ist? –

+0

Können Sie ein Beispiel für einen Anwendungsfall geben, um zu demonstrieren, wie Sie das "Objekt" initialisieren und wie das 'fire()' aufgerufen wird? BTW, in Ihrem 'std :: vector' Beispiel sollten Sie' std :: move (listeners) 'oder mindestens' std :: swap() 'benutzt haben. – iammilind

+0

Typ löschen ist dein Freund. – StoryTeller

Antwort

2

würde ich abraten dies:


In Ihrem Code würde es so etwas wie dieses (nicht getestet) sein. Ihr anfängliches Design scheint hervorragend für Beobachter zu sein - es entkoppelt die zwei verschiedenen Teile des Designs sehr gut. Das Einführen von Vorlagen bedeutet, dass jeder alle Zuhörer kennen muss, die Ihr Object enthält. Also stellen Sie sicher, dass Sie das wirklich zuerst machen wollen.


Das heißt, Sie sind für einen heterogenen Container-Typen suchen - std::tuple. Storage ist einfach:

template<typename... Listeners> 
struct Object 
{ 
    std::tuple<Listeners...> listeners; 

    Object(Listeners const&... ls) 
    : listeners(ls...) 
    { } 
}; 

Firing beinhaltet die Verwendung des index_sequence Trick (diese Klasse nur eingeführt wurde in C++ 14 kann aber C++ 11 implementiert werden, indem). Here ist eine gute Antwort zu erklären, was vor sich geht.

public: 
    void fire() { 
     fire(std::index_sequence_for<Listeners...>{}); 
    } 

private: 
    template <size_t... Is> 
    void fire(std::index_sequence<Is...>) { 
     using swallow = int[]; 
     (void)swallow{0, 
      (void(std::get<Is>(listeners).fire()), 0)... 
      }; 
    } 

Mit C++ 14 generic lambdas, ist es einfacher, eine generische for_each_tuple zu schreiben:

template <class Tuple, class F, size_t... Is> 
void for_each_tuple(Tuple&& tuple, F&& func, std::index_sequence<Is...>) { 
    using swallow = int[]; 
    (void)swallow{0, 
     (void(std::forward<F>(func)(std::get<Is>(std::forward<Tuple>(tuple)))), 0)... 
     }; 
} 

template <class Tuple, class F> 
void for_each_tuple(Tuple&& tuple, F&& func) { 
    for_each_tuple(std::forward<Tuple>(tuple), std::forward<F>(func), 
     std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{} 
     ); 
} 

Und jetzt Ihre fire() wird:

void fire() { 
    for_each_tuple(listeners, [](auto& l) { l.fire(); }); 
} 
2

Sie können eine std::tuple verwenden, um heterogene Objekte zu speichern.

Um darüber zu iterieren, hier ist einige Vorlage Magie. Dadurch wird die Funktorvorlage für jedes Objekt aufgerufen (der Funktor passt sich also an verschiedene Typen an). SFINAE wird verwendet, um zu bestimmen, wann die Iteration gestoppt werden soll. Variablen From und Um den Bereich zu definieren, wird From bei jeder Iteration inkrementiert. Wenn sie gleich sind, muss die Iteration stoppen, deshalb muss in diesem Fall eine leere Funktion vorhanden sein.

#include <tuple> 
#include <type_traits> 
#include <cstddef> 

template <template <typename> class Functor, typename Tuple, std::size_t From, std::size_t To> 
typename std::enable_if<From == To, void>::type for_each(const Tuple &t) {} 

template <template <typename> class Functor, typename Tuple, std::size_t From = 0, std::size_t To = std::tuple_size<Tuple>::value> 
typename std::enable_if<From < To, void>::type for_each(const Tuple &t) { 
    Functor<typename std::tuple_element<From, Tuple>::type> op; 
    op(std::get<From>(t)); 
    for_each<Functor, Tuple, From + 1, To>(t); 
} 

Sie benötigen einen Funktor Vorlage zu schreiben, die Sie es passieren, wie:

template <typename T> 
struct Functor { 
    void operator()(const T &x) { 
     // ... 
    } 
} 

, die für jedes Objekt aufgerufen wird.

template <typename Listener> 
struct FireFunctor { 
    void operator()(const Listener &x) { 
     x.fire(); 
    } 
} 

template<typename ... Listeners> 
struct Object 
{ 
    std::tuple<Listeners...> store; 

    Object(Listeners&&...listeners) 
    { 
     store = std::make_tuple(listeners...); 
    } 

    void fire() 
    { 
     for_each<FireFunctor>(store); 
    } 
};