2017-08-22 55 views
7

Mam proces mieszania zaimplementowany przy użyciu Howard Hinnant's method (ogólny skrót na podstawie przeciążenia hash_append).Hashowanie typu polimorficznego we właściwy sposób

Celem tej metody jest utworzenie skrótu klas w celu "zapamiętania" wyniku obliczeń (patrz koniec tej odpowiedzi), więc mam do czynienia z pewnym problemem. W szczególności należy wziąć pod uwagę następujące możliwe Input klasę, która musi być mieszany:

struct A { 
    virtual int do_stuff() const = 0; 
    virtual ~A(); 
}; 
struct B: A { 
    int do_stuff() const override { return 0; } 
}; 
struct C: A { 
    const int u; 
    int do_stuff() const override { return u; } 
}; 

struct Input { 
    A const& a; // store a reference to an instance of B or C 
}; 

Teraz, jeśli chcę hash Input będę mieć coś takiego:

template <class HashAlgorithm> 
void hash_append(HashAlgorithm& h, Input const& input) { 
    hash_append(h, typeid(input)); 
    hash_append(h, typeid(input.a)); 
} 

Więc muszę przeciążenie hash_append dla A:

template <class HashAlgorithm> 
void hash_append(HashAlgorithm& h, A const& a) { 
    hash_append(h, typeid(a)); 
} 

tu problem jest to, że w zależności od rodzaju środowiska wykonawczego a, musiałbym dodać dodatkowe informacje do skrótu, np. dla C musiałbym dodać u.

I myślał o następujących rozwiązań (i ujemnych):

  1. dodać metodę wirtualną A które zwraca szczególną wartość, która może być dodana do typeid() mieszania, ale:

    • oznacza to dodanie metody wewnątrz A, która nie jest związana z celem A, więc nie podoba mi się ten pomysł (w szczególności dlatego, że mam wiele klas podobnych do A);
    • to łamie koncepcję hash_append, ponieważ metoda będzie miała unikalny typ zwrotu dla wszystkich dziedziczących klas.
  2. zrobić kilka dynamic_cast wewnątrz hash_append:

    • Znalazłem to dość brzydki ... zwłaszcza jeśli mam wiele klas podobne do A;
    • jest to podatne na błędy: jeśli ktoś doda nowe dziecko o numerze A i nie dodaje dynamicznej zmiany wewnątrz hash_append.

Czy istnieje sposób do mieszania typ polimorficzny, bez konieczności zmiany rodzaju lub powoływać się na pęczek dynamic_cast?


Ostatecznym celem jest to, aby móc memoize wyniki niektórych ciężkich funkcji. Załóżmy szkic podstawowej struktury mojego wniosku:

struct Input { }; 
struct Result { }; 

Result solve(Input const&); 

Funkcja solve jest obliczeniowo-ciężki, więc chcę, aby zapisać wyniki poprzedniego obliczenia w pliku przy użyciu skrótu Input s, na przykładcoś takiego:

// depends on hash_append 
std::string hash(Input const&); 

Result load_or_solve(Input const& input) { 
    auto h = hash(input); 
    Result result; 
    if (exists(h)) { // if result exists, load it 
     result = load(h); 
    } 
    else { // otherwize, solve + store 
     result = solve(input); 
     store(h, result); 
    } 
    return result; 
} 

W load i store metody będzie ładować i przechowywać wyniki z plików, celem jest, aby memoize rozwiązań między różnymi przebiegów.

Jeśli masz sugestie, jak zapamiętać te wyniki bez zmieniania się z powyższymi problemami, chętnie je przeczytam.

+1

można użyć podwójnego dyspozytorskich w wersji '' hash_append' z A' i wysyła żądanie do odpowiednich wersji dla '' C i B' 'kiedy odzyskałeś typ, ale nie sądzę, że dokładnie to czego szukasz. Czy rozważałeś to? Wadą jest to, że musisz dodać do nich te bloki, aby przyjąć gościa. Jeśli może ci pomóc, mogę umieścić komentarz w bardziej szczegółowej odpowiedzi. – skypjack

+0

@skypjack Przepraszam, nie rozumiem, co masz na myśli - czy możesz napisać mały przykład ilustrujący twoje znaczenie? – Holt

+1

Mam na myśli coś [wzdłuż tej linii] (http://coliru.stacked-crooked.com/a/c1b5c249535f7590). To nie jest działający przykład, ale powinieneś dostać pomysł. Niestety twój przykładowy kod nie zawiera wielu kodów, aby spakować konkretny przykład, przepraszam. Oczywiście, 'HashVisitor' może zostać uproszczony (przynajmniej zaprojektowany tak, aby nie trzeba go modyfikować za każdym razem, gdy definiujesz nowy typ) za pomocą szablonów variadycznych i dziedziczenia bezpośredniego, ale sposób, w jaki go zdefiniowałem powinien być łatwiejszy rozumieć. – skypjack

Odpowiedz

4

można użyć podwójnego dyspozytorskich w wersji Ahash_append i przekazuje wniosek do odpowiedniej wersji (czyli jeden albo dla B lub C). Wadą jest to, że musisz przydzielić te klasy, aby przyjąć gościa i nie mogę powiedzieć, czy jest to dla ciebie do przyjęcia.
Oto garść kodu, który powinien zilustrować ideę:

struct B; 
struct C; 

struct Visitor { 
    virtual void visit(const B &) = 0; 
    virtual void visit(const C &) = 0; 
}; 

template<typename T, typename... O> 
struct HashVisitor: T, HashVisitor<O...> { 
    template<typename U> 
    std::enable_if_t<std::is_same<T, U>::value> tryVisit(const U &u) { 
     T::operator()(u); 
    } 

    template<typename U> 
    std::enable_if_t<not std::is_same<T, U>::value> tryVisit(const U &u) { 
     HashVisitor<O...>::visit(u); 
    } 

    void visit(const B &b) override { tryVisit<B>(b); } 
    void visit(const C &c) override { tryVisit<C>(c); } 
}; 

template<> 
struct HashVisitor<>: Visitor {}; 

template<typename... F 
auto factory(F&&... f) { 
    return HashVisitor<std::decay_t<F>>{std::forward<F>(f)...}; 
} 

struct A { 
    virtual void accept(Visitor &) = 0; 
    virtual int do_stuff() const = 0; 
    virtual ~A(); 
}; 

struct B: A { 
    void accept(Visitor &v) override { v.visit(*this); } 
    int do_stuff() const override { return 0; } 
}; 

struct C: A { 
    const int u; 
    void accept(Visitor &v) override { v.visit(*this); } 
    int do_stuff() const override { return u; } 
}; 

template <class HashAlgorithm> 
void hash_append(HashAlgorithm &, const B &) { 
    // do something 
} 

template <class HashAlgorithm> 
void hash_append(HashAlgorithm &, const C &) { 
    // do something 
} 

template <class HashAlgorithm> 
void hash_append(HashAlgorithm &h, const A &a) { 
    auto vis = factory(
     [&h](const B &b){ hash_append(h, b); }, 
     [&h](const C &c){ hash_append(h, c); } 
    ); 

    a.accept(vis); 
} 
+0

Dzięki za odpowiedź - myślę, że jest to najlepsza odpowiedź, jeśli chcę solidności, tj. Nie mając typu z niezautomatyzowaną metodą hash. Niestety, muszę przyjąć przeciwny kierunek, tj. Mniej niezawodną metodę (w czasie kompilacji), ale z mniejszymi ograniczeniami: zazwyczaj nie mogę przesyłać dalej - deklaruję klasy przed gościem, i nadal chciałbym oddzielić proces mieszania od klasyfikuj się. Próbuję zaimplementować coś, co pozwoliłoby mi dodać niestandardową wysyłkę środowiska wykonawczego po zadeklarowaniu klas (jestem trochę utknięty w tym czasie, może wkrótce otworzyć inne pytanie ...). – Holt

+0

Daj mi znać, a ja postaram się odpowiedzieć, jeśli to możliwe. – skypjack

+0

To tutaj - https://stackoverflow.com/questions/45837117/storing-various-tuples-and-applying-templated-functor-on-them – Holt