2015-12-21 56 views
9

Chcę zaimplementować menedżera, który przechowuje wywołania zwrotne do funkcji składowych klas polimorficznych za pomocą C++ 11. Problem polega na tym, że nie jestem pewien, jak poradzić sobie z przypadkiem, w którym obiekt, do którego należy członek, zostanie usunięty lub powinien zostać usunięty, a ja chcę uczynić interfejs tak prostym, jak to tylko możliwe.Używanie std :: function i std :: bind do przechowywania wywołania zwrotnego i obsługi usuwania obiektu.

Więc pomyślałem o następujących rzeczach: Store std::weak_ptr do obiektu, a także std::function do członka.

Poniższy wydaje się działać:

class MyBase { 
public: 
    MyBase() {} 
    virtual ~MyBase() {} 
}; 
//-------------------------------------------------- 

class MyClass : public MyBase { 
public: 
    MyClass() : MyBase() {} 
    void myDouble(double val) const { std::cout << "Value is: " << val << std::endl; } 
}; 
//-------------------------------------------------- 

Class Manager { 
public: 
    void setFunction(std::weak_ptr<MyBase> base, std::function<void(double)> func) { 
     m_function.first = base; 
     m_function.second = func; 
    } 
private: 
    std::pair<std::weak_ptr<MyBase>, std::function<void(double)>> m_function; 
}; 

Aby tego używać:

Manager db; 
std::shared_ptr<MyClass> myClass = std::make_shared<MyClass>(); 
db.setFunction(myClass, std::bind(&MyClass::myDouble, myClass, std::placeholders::_1)); 

Teraz chcę ukryć std::bind część od użytkownika tak, że on potrzebuje tylko zadzwonić:

db.setFunction(myClass, &MyClass::myDouble); 

Więc chcę uzyskać prawie następujące czynności w mojej funkcji menedżera:

void setFunc2(std::weak_ptr<MyBase> base, std::function<void(double)> func) { 
    m_function.first = base; 
    m_function.second = std::bind(func, base, std::placeholders::_1); 
} 

Ale przede daje błędy:

error: no match for 'operator=' (operand types are 'std::function<void(double)>' and 
'std::_Bind_helper<false, std::function<void(double)>&, std::weak_ptr<MyBase>&, const std::_Placeholder<1>&>::type {aka std::_Bind<std::function<void(double)>(std::weak_ptr<MyBase>, std::_Placeholder<1>)>}') 
     m_function.second = std::bind(func, base, std::placeholders::_1); 

Czy istnieje lepszy sposób to zrobić, a może sposób, aby dostać tę pracę?

Coś interesującego, co zauważam. Jeśli użyję std::shared_ptr, use_count() zostanie zwiększony o wywołanie std::bind w oryginalnym kodzie. W związku z tym nie mogę ręcznie zresetować/zniszczyć obiektu, chyba że usunę element z mojego menedżera. Gdzie jest to udokumentowane zachowanie, zwykle używam cppreference?

Mam spojrzał na następującym pytaniem ale nie wydaje się uzyskać go pracy dla mojego problemu: How can I use polymorphism with std::function?

+0

chodzi shared_ptr licznika wykorzystywanych http://en.cppreference.com/w/cpp/utility/functional/bind mówi " Zwracany typ std :: bind zawiera [...] jeden obiekt na każdy z argumentów ..., [...] skonstruowanych ze std :: forward (arg_i). " i niżej na tej samej stronie "Argumenty do powiązania są kopiowane lub przenoszone,". Od momentu podania wartości zmiennej shared_ptr masz kopię. – Cubbi

+1

'& MyClass :: myDouble' nie będzie konwertować na' std :: function 'mimo to. –

+0

@ T.C W pełni zdaję sobie z tego sprawę, a więc powód pytania. Nie obchodzi mnie, jak wygląda podpis, chcę, żeby wywołanie wyglądało jak 'db.setFunction (myClass i MyClass :: myDouble);' –

Odpowiedz

7

Template setFunction tak, że można przyjąć wskaźnik do członka-of-pochodzić, a nie robić napisać 12 przeciążeń dla kombinacji kwalifikatorów cv/ref.

template<class D, class D2, class F> 
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) { 
    // optionally static_assert that D2 is a base of D. 
    m_function.first = sp; 
    m_function.second = std::bind(member, sp.get(), std::placeholders::_1); 
} 

Oczywiście trzeba się upewnić, że lock()m_function.first przed wywołaniem m_function.second.

Alternatywnie, wystarczy użyć lambda, który przechwytuje zarówno wskaźnik weak_ptr a funkcja użytkownika:

std::function<void(double)> m_function; 

template<class D, class D2, class F> 
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) { 
    std::weak_ptr<D> wp = sp; 
    m_function = [wp, member](double d) { 
     if(auto sp = wp.lock()){ 
      ((*sp).*member)(d); 
     } 
     else { 
      // handle pointer no longer valid case. 
     } 
    }; 
} 
+0

Na szczęście kompilator rzucił błąd 'nie można przekonwertować 'random * const' to ' MyBase * "w zadaniu". W interesie, jak taki "static_assert" powinien wyglądać tak: 'static_assert (std :: is_base_of :: value == true," Obiekt musi być klasą pochodną \ 'MyBase \' ";' –

+0

Czy istnieje inteligentny sposób obsługi 'db.setFunction (myClass, nullptr);'. Dla przykładu nie ma to sensu, ale w moim prawdziwym kodzie działa. –

+0

@TheBadger Zdefiniuj "uchwyt". –

1

Lubię oddzielenie mojego słuchacza/nadawcy z realizacji słuchacza.

Oznacza to, że nie mogę umieścić wymagań na słuchaczu. Nie może wymagać przydzielenia słuchacza w określony sposób.

Najprostszą metodą, jaką znalazłem, to nadanie przez nadawcę tokena, którego okres istnienia określa czas trwania połączenia.

using token = std::shared_ptr<void>; 

template<class...Args> 
struct broadcaster { 
    using target = std::function<void(Args...)>; 
    using wp_target = std::weak_ptr<target>; 
    using sp_target = std::shared_ptr<target>; 
    static sp_target wrap_target(target t) { 
    return std::make_shared<target>(std::move(t)); 
    }; 

    token start_to_listen(target f) { 
    auto t = wrap_target(std::move(f)); 
    targets.push_back(t); 
    return t; 
    } 
    void broadcast(Args... args) { 
    targets.erase(
     std::remove_if(targets.begin(), targets.end(), 
     [&](wp_target t)->bool { return t.lock(); } 
    ), 
     targets.end() 
    ); 
    auto targets_copy = targets; // in case targets is modified by listeners 
    for (auto wp : targets_copy) { 
     if (auto sp = wp.lock()) { 
     (*sp)(args...); 
     } 
    } 
    } 
    std::vector<wp_target> targets; 
}; 

to zmusza ludzi, którzy rejestrują słuchaczy do utrzymywania w pobliżu std::shared_ptr<void>.

Możemy nawet uczynić go bardziej wyszukanym, gdzie zniszczenie ostatniego shared_ptr<void> faktycznie usuwa słuchacza z listy natychmiast. Ale powyższe leniwy wyrejestrowanie wydaje się działać dość dobrze w moim doświadczeniu i jest stosunkowo łatwe, aby był przyjazny dla wielu wątków. (jednym poważnym problemem jest to, co dzieje się, gdy zdarzenie broadcast usuwa lub dodaje rzeczy do listy słuchaczy: dostosowanie powyższego do działania jest łatwe i przyjemne z zasadą, że słuchacze dodawani podczas nadawania nie otrzymują transmisji, a słuchacze usunięci podczas transmisja nie dostać transmisję. Słuchaczy usunięte jednocześnie podczas transmisji można uzyskać transmisję w większości moich wdrożeń ... to staje się kosztowne, aby uniknąć.)


Moglibyśmy zamiast oddzielić to inaczej. Słuchacz może przekazać std::function i std::weak_ptr osobno do nadawcy, który przechowuje i tylko wywołuje std::function, jeśli std::weak_ptr jest ważny.

0

Lubię podejście Yakka. Oto zaktualizowana wersja, która naprawia kilka problemów z kompilacją (np. Nie może nazwać funkcji "zarejestruj się"). Dodaje również metodę rm_callback dla klientów, aby łatwo usunąć ich rejestrację bez zmuszania tokena rejestracyjnego do wykroczenia poza zakres lub znajomości elementów wewnętrznych. Nie lubiłem skanować listy za każdym razem, gdy wydarzenie było transmitowane, więc dodałem deleter na udostępnionym wskaźniku, który wykonuje zadanie czyszczenia. Wszystkie nowe błędy lub nieefektywności są moje. Czytelnik alert powinny być świadome zagadnień gwintów podczas modyfikowania listy podczas nadawanie ...

using token = std::shared_ptr<void>; 
template<class...Args> 
struct broadcaster { 
    using target = std::function<void(Args...)>; 
    using wp_target = std::weak_ptr<target>; 
    using sp_target = std::shared_ptr<target>; 

    token add_callback(target f) { 
     sp_target t(new target(std::move(f)), [&](target*obj) { delete obj; cleanup(); }); 
     targets.push_back(t); 
     return t; 
    } 

    static void rm_callback(token& t) 
    { 
     t.reset(); 
    } 

    void cleanup() 
    { 
     targets.erase(
      std::remove_if(targets.begin(), targets.end(), 
       [](wp_target t) { return t.expired(); } 
      ), 
      targets.end() 
     ); 
    } 

    void broadcast(Args... args) { 
     for (auto wp : targets) { 
      if (auto sp = wp.lock()) { 
       (*sp)(args...); 
      } 
     } 
    } 

    std::vector<wp_target> targets; 
}; 

// declare event taking a string arg 
broadcaster<std::string> myEvent; 
+1

Twój 'add_callback' zakłada, że ​​nadawca przeżywa token odbiornika, niejawnie przechwytując' this'. Jeśli twoja lambda przeżywa zasięg lokalny, nigdy nie używaj '[&]' jak ukrywa takie zależności. Po drugie, prawdopodobnie chcesz * skopiować * 'cele' w' broadcast', tak jakby lista nadawców była modyfikowana przez detektor, a niezaliczone pętle 'for (:)' zawiodą. Mój też miał jeden z tych błędów; naprawiłem to w moim. – Yakk