2017-12-27 120 views
7

Powiedzmy, że masz klasę foo, która opakowuje kolekcję niektórych obiektów do wywołania. foo ma funkcję składową run(), która wykonuje iterację nad kolekcją i wywołuje każdy obiekt funkcji. foo ma także członka remove(...), który usunie obiekt wywoływalny z kolekcji.W języku C++ istnieje idiomatyczny sposób ochrony przed sytuacją, w której uruchomienie zbioru działań powoduje zbieranie kolekcji?

Czy istnieje idiomatyczne, straż RAII stylu można umieścić w foo.run() i foo.remove(...) taki sposób, że usuwa, które były napędzane przez wywołanie foo.run() zostanie odroczone do pożarów destructor strażnika? Czy można to zrobić za pomocą czegoś w standardowej bibliotece? Czy ten wzór ma nazwę?

Mój obecny kod wydaje się nieelegancki, dlatego szukam rozwiązania typu "najlepsza praktyka".

Uwaga: nie dotyczy to współbieżności. Rozwiązanie bez wątków jest w porządku. Kwestia dotyczy ponownego entrancy i samo-odniesienia.

Oto przykład problemu, nie ma nieeleganckiego "odsuń" strażnika.

class ActionPlayer 
{ 
private: 
    std::vector<std::pair<int, std::function<void()>>> actions_; 
public: 
    void addAction(int id, const std::function<void()>& action) 
    { 
     actions_.push_back({ id, action }); 
    } 

    void removeAction(int id) 
    { 
     actions_.erase(
      std::remove_if(
       actions_.begin(), 
       actions_.end(), 
       [id](auto& p) { return p.first == id; } 
      ), 
      actions_.end() 
     ); 
    } 

    void run() 
    { 
     for (auto& item : actions_) { 
      item.second(); 
     } 
    } 
}; 

wtedy gdzie indziej:

... 

ActionPlayer player; 

player.addAction(1, []() { 
    std::cout << "Hello there" << std::endl; 
}); 

player.addAction(42, [&player]() { 
    std::cout << "foobar" << std::endl; 
    player.removeAction(1); 
}); 

player.run(); // boom 

Edytuj ... w porządku to jak widzę to zrobić za pośrednictwem obiektu blokady RAII. Następujące powinny obsługiwać akcje wyrzucania i ponownego wejścia do wywołań w ramach działania, zakładając, że rekurencja zostanie ostatecznie zakończona (jeśli nie jest to wina użytkownika). Używałem buforowanych std :: functions, ponieważ w prawdziwej wersji tego kodu odpowiednik addAction i removeAction to funkcje szablonu, które nie mogą być przechowywane w jednorodnym opakowaniu waniliowym.

+0

Dlaczego nie pokazać swoją "nieeleganckie 'odroczyć usuń' strażnika"? To brzmi jak sposób, w jaki podchodzę do tego. – 1201ProgramAlarm

+0

Ponieważ mam to w moim prawdziwym kodzie, nie ta wersja zabawki i jest związana z prawdziwymi strukturami, które używają szablony variadic itp. To jest w pewnym sensie problem. Próbowałem znaleźć bardziej ogólne rozwiązanie, nie takie, które musi wiedzieć o wewnętrznych klasach podobnych do ActionPlayer. W każdym razie źle zaktualizuję ten przykład, gdy dostanę trochę czasu. – jwezorek

Odpowiedz

1

Zwykły sposób polega na utworzeniu kopii urządzenia vector. Ale może to spowodować ponowne uruchomienie usuniętych akcji.

void run() 
{ 
    auto actions_copy{actions_}; 
    for (auto& item : actions_copy) { 
     item.second(); 
    } 
} 

Inne opcje, jeśli działa usunięte działanie jest niedozwolone

  1. Dodaj bool przechowywać jeśli niektóre działania jest usuwany
  2. Zastosowanie shared/słaby ptr
  3. korzystanie std::list jeśli wiadomo, że bieżąca akcja nie zostanie usunięta.
+0

hmmm ... kopiowanie jest proste, ale miałem nadzieję na coś, co nie przydziela pamięci w run() w większości przypadków, w których samo-odwołanie się nie zdarza. Nie sądzę, że przełączenie się na std :: list rozwiązuje problem. – jwezorek

+0

Następnie powinieneś iść z dodaniem 'bool' do zapisania, jeśli akcja jest oznaczona jako usunięta i usunąć je na końcu' run' – balki

1

Dodaj flagę do run, która mówi, że wyliczasz poprzez actions_. Następnie, jeśli wywoływane jest removeAction z tym zestawem flag, przechowujesz id w wektorze do późniejszego usunięcia. Może być również potrzebny osobny wektor do przechowywania działań dodawanych podczas wyliczania. Gdy skończysz testowanie przez actions_, usuniesz te, które chcesz usunąć i dodasz te, które zostały dodane.

Coś

// within class ActionPlayer, add these private member variables 
private: 
    bool running = false; 
    std::vector<int> idsToDelete; 

public: 
    void run() { 
     running = true; 
     for (auto& item : actions_) { 
      item.second(); 
     } 
     running = false; 
     for (d: idsToDelete) 
      removeAction(d); 
     idsToDelete.clear(); 
    } 

    // ... 

Można zrobić podobną zmianę tytułu odroczonego addAction połączeń (które będzie trzeba zrobić, jeśli któryś z działań można dodać akcję, ponieważ dodatek może powodować wektor przeznaczyć więcej pamięci, unieważniając wszystkie iteratory do wektora).

+0

, jeśli jedna z akcji rzuca, że ​​będziesz pozostawiony w nieokreślonym stanie. – jwezorek

+0

@jwezorek: więc umieść go w try/catch lub użyj 'unique_ptr >' do automatycznego czyszczenia –

0

Zmodyfikuję strukturę nieco. Zamiast pozwolić na bezpośrednią modyfikację ActionPlayer, wymuszałbym wszystkie modyfikacje za pomocą zewnętrznej klasy modyfikującej. W tym przykładzie stworzyłem abstrakcyjną klasę modyfikatorów, która może mieć różne implementacje z konkretami (na przykład DeferredModifier, InstantModifier, NullModifier, LoggedModifier, TestModifier .etc.). Twoje działania muszą teraz tylko odwoływać się do abstrakcyjnej klasy bazowej modyfikatora i wywoływać dowolny add/remove .etc. w razie potrzeby funkcjonuj w ten sposób. Pozwala to na oddzielenie polityki modyfikacji od implementacji działania i wprowadzenie różnych strategii modyfikacji do działań.

Powinno to również pozwolić na prostsze wsparcie dla równoczesnej modyfikacji, ponieważ nie trzeba już przełączać stanu uruchamiania/niedziałającego, aby odroczyć modyfikacje.

Ten przykład pokazuje prosty sposób odtwarzania akcji w kolejności (która jest właściwością, którą zakładam, którą chcesz zachować). Bardziej zaawansowana implementacja może skanować listę modyfikacji do tyłu, usuwając wszystkie pary dodawania/usuwania, a następnie modyfikując listę działań, a następnie modyfikując/usuwając grupy, aby zminimalizować kopiowanie.

Coś jak:

class ActionPlayer { 
friend class Modifier; 
... 

    void run(Modifier &modifier) { } 
private: 
    void addAction(...) { ... } 
    void removeAction(...) { ... } 
} 

class Modifier 
{ 
public: 
    virtual ~Modifier() {} 
    virtual addAction(...) = 0; 
    virtual removeAction(...) = 0; 
} 

class DelayedModifier : public Modifier 
{ 
    struct Modification { virtual void run(ActionPlayer&) = 0; } 

    struct RemoveAction : public Modification 
    { 
     int id; 

     Removal(int _id) : id(_id) {} 
     virtual void run(ActionPlayer &player) { player.removeAction(id); } 
    } 

    struct AddAction : public Modification 
    { 
     int id; 
     std::function<void(Modifier&)>> action; 

     AddAction(int _id, const std::function<void(Modifier&)> &_action) : id(_id), action(_action) {} 
     virtual void run(ActionPlayer &player) { player.addAction(id, action) }; 
    } 

    ActionPlayer &player; 
    std::vector<Modification> modifications; 

public: 
    DelayedModifier(ActionPlayer &_player) player(_player) {} 
    virtual ~DelayedModifier() { run(); } 

    virtual void addAction(int id, const std::function<void(Modifier&)> &action) { modifications.push_back(AddAction(id, action)); } 
    virtual void removeAction(int id) { modifications.push_back(RemoveAction(id)); } 

    void run() 
    { 
     for (auto &modification : modifications) 
      modification.run(player); 
     modifications.clear(); 
    } 
}; 

Więc teraz napisać:

ActionPlayer player; 

{ 
    DelayedModifier modifier(player); 

    modifier.addAction(...); 
    modifier.addAction(...); 
    modifier.run(); 
    actions.run(modifier); 
} 
Powiązane problemy