2008-11-13 18 views
6

Pracuję nad aplikacją C++, która wewnętrznie zawiera obiekty kontrolerów, które są regularnie tworzone i niszczone (przy użyciu nowych). Konieczne jest, aby kontrolery rejestrowały się w innym obiekcie (nazwijmy go kontrolerem nadrzędnym) i wyrejestruj się, gdy zostaną zniszczone.Wymuszenie zniszczenia czegoś ostatnio w C++

Problem, który napotykam obecnie, ma miejsce, gdy opuszczam aplikację: ponieważ kolejność destrukcji nie jest deterministyczna, zdarza się, że instancja pojedynczego kontrolera Monitora jest niszczona przed (niektórymi) z samych kontrolerów, a kiedy wywołują metoda wyrejestrowania w ich destruktorze, robią to na już zniszczonym obiekcie.

Jedyny pomysł, jaki wymyśliłem do tej pory (mający duże przeziębienie, więc to może nie oznaczać zbyt wiele) nie ma kontrolera kontrolera jako zmiennej globalnej na stosie, ale raczej na stercie (to znaczy przy użyciu nowego). Jednak w tym przypadku nie mam miejsca, aby je usunąć (wszystko to znajduje się w bibliotece innej firmy).

Wszelkie wskazówki/sugestie dotyczące możliwych opcji zostałyby docenione.

+0

według "3rd party rodzaj biblioteki" masz na myśli, że nie piszesz "main"? –

+0

Oznacza to, że kod kontrolera i nadzorcy znajduje się w statycznie połączonej bibliotece. Opiekun nie jest widoczny poza tą biblioteką, ale kontrolery są tworzone przez aplikację. – Steven

+0

Jeśli utworzysz kontrolery, możesz kontrolować ich zniszczenie, prawda? – Null303

Odpowiedz

2

Można użyć wzorca obserwatora. Kontroler informuje swojego przełożonego, że jest zniszczony. I Supervisor przekazuje to samo dziecku po zniszczeniu.

Spójrz na http://en.wikipedia.org/wiki/Observer_pattern

+0

To brzmi dobrze. Ale czy jest bezpieczny dla wątków? (pozwólcie kontrolerom i nadzorcy być w różnych wątkach) – Steven

+0

To już wygląda jak wzorzec obserwatora, mógł użyć faktu, że przełożony ma już odniesienie do jego "obserwatorów" – Null303

+0

Powinien być bezpieczny dla wątków, o ile tylko Supervisor niszczy każdego, kontrolery mogą zakończyć to, nad czym pracują, zanim zostaną zniszczone, a kontrolerzy mogą zniszczyć się bez dalszej interakcji z Supervisor (aby uniknąć impasu). –

5

Kolejność niszczenia zmiennych automatycznych (które obejmują "normalne" zmienne lokalne używane w funkcjach) jest w odwrotnej kolejności do ich utworzenia. W takim przypadku umieść kontroler Supervisor na górze.

Porządek destrukcji globali pojawia się również na odwrocie ich tworzenia, co z kolei zależy od kolejności, w jakiej są zdefiniowane: Późniejsze zdefiniowane obiekty są tworzone później. Ale uwaga: Obiekty zdefiniowane w różnych plikach .cpp (jednostki tłumaczeniowe) nie mają gwarancji utworzenia w zdefiniowanej kolejności.

myślę, że należy rozważyć wykorzystanie go jak Mike poleca:

  1. Stworzenie odbywa się za pomocą wzorca singleton (ponieważ kolejność inicjalizacji obiektów w różnych jednostkach translacji nie są zdefiniowane) po pierwszym użyciu, zwracając wskaźnik do obiektu sterującego funkcyjno-statycznego.
  2. Osoba nadzorująca jest zwykle niszczona (przy użyciu reguł dotyczących niszczenia elementów statycznych w funkcjach). kontrolerzy wyrejestrowują się za pomocą statycznej funkcji przełożonego. Ten sprawdza, czy administrator jest już zniszczony (sprawdzanie wskaźnika dla != 0). Jeśli tak, to nic się nie robi. W przeciwnym razie przełożony zostanie powiadomiony.

Ponieważ wyobrażam sobie, że może istnieć osoba nadzorująca bez podłączonego kontrolera (i to tylko tymczasowo), inteligentny wskaźnik nie może zostać użyty do automatycznego zniszczenia osoby nadzorującej.

+0

controllerSupervisor jest zmienną globalną. Kontrolery są tworzone przy użyciu nowego. Jednak kontroler nadrzędny nie działa przed ostatnim kontrolerem (co może być spowodowane tym, że jeden z kontrolerów jest częścią innego globalnego obiektu, ale nie można tego uniknąć, a kolejność jest losowa z powodu łączenia) – Steven

+0

@Steven: Jeśli możesz polegaj na kolejności stosów, musisz samodzielnie zarządzać sfinalizowaniem. Czy istnieje powód, dla którego kontroler Superwizor nie może wyczyścić kontrolerów? – eduffy

0

można zrobić jedną z następujących czynności w zależności od okoliczności.

  1. Użyj wzorca obserwatora zgodnie z sugestią gurin. Zasadniczo nadzorca informuje kontrolerów, że jedzie w dół ...
  2. Niech nadzorca "będzie właścicielem" Kontrolerów i będzie odpowiedzialny za ich zniszczenie, gdy przejdzie w dół.
  3. Trzymaj kontrolerów w shared_pointers, więc ktokolwiek schodzi na ostatek, dokona prawdziwego zniszczenia.
  4. zarządzać zarówno (inteligentne kursory) do kontrolerów i przełożonego na stosie, który pozwoli Ci określić kolejność zniszczenie
  5. innych ...
0

Można spojrzeć za pomocą numeru zarejestrowanych kontrolerów jako wartowników do faktycznego usunięcia.

Wywołanie kasowania jest wówczas tylko żądaniem i należy poczekać, aż kontrolerzy wyrejestrują się.

Jak wspomniano, jest to jedno użycie wzoru obserwatora.

class Supervisor { 
public: 
    Supervisor() : inDeleteMode_(false) {} 

    void deleteWhenDone() { 
     inDeleteMode_ = true; 
     if(controllers_.empty()){ 
      delete this; 
     } 
    } 

    void deregister(Controller* controller) { 
     controllers_.erase(
      remove(controllers_.begin(), 
         controllers_.end(), 
         controller)); 
     if(inDeleteMode_ && controllers_.empty()){ 
      delete this; 
     } 
    } 


private: 

    ~Supervisor() {} 
    bool inDeleteMode_; 
    vector<Controllers*> controllers_; 
}; 

Supervisor* supervisor = Supervisor(); 
... 
supervisor->deleteWhenDone(); 
+0

Dzięki, ale nie mogę usunąć nadzorcy ręcznie (jest on tworzony w statycznej bibliotece, która nie ma tego, co możesz uważać za główną() rutynę - więc nie mam miejsca, aby zadzwonić do przełożonego-> deleteWhenDone () bez zerwania jakiejś separacji w kodzie) – Steven

0

To nie do końca elegancki, ale można zrobić coś takiego:

struct ControllerCoordinator { 
    Supervisor supervisor; 
    set<Controller *> controllers; 

    ~ControllerDeallocator() { 
     set<Controller *>::iterator i; 
     for (i = controllers.begin(); i != controllers.end(); ++i) { 
      delete *i; 
     } 
    } 
} 

Nowa globalna:

ControllerCoordinator control; 

Wszędzie zbudować kontroler, dodać control.supervisor.insert(controller). Wszędzie, gdzie ją zniszczysz, dodaj control.erase(controller). Może być możliwe uniknięcie prefiksu control. poprzez dodanie globalnego odwołania do control.supervisor.

Osoba nadzorująca koordynatora nie zostanie zniszczona przed uruchomieniem destruktora, więc masz gwarancję, że przełożony przeżyje kontrolerów.

1

Kilka sugestii:

  • uczynić controllerSupervisor pojedyncza (lub owinąć go w jednoelementowy obiektu tworzonego na ten cel), która jest dostępna za pośrednictwem statycznej metody, która zwraca wskaźnik, wówczas dtors z zarejestrowane obiekty mogą wywoływać statyczny akcesor (który w przypadku wyłączenia aplikacji i kontroler, który został zniszczony, zwróci wartość NULL), a obiekty te mogą w tym przypadku uniknąć wywołania metody de-register.

  • utwórz sterownik kontrolera na stercie za pomocą nowego i użyj czegoś w stylu boost::shared_ptr<>, aby zarządzać jego życiem. Rozdaj shared_ptr<> w statycznej metodyce singleton's accessor.

0

Spraw, aby osoba nadzorująca cotrol była singltonem. Upewnij się, że konstruktor sterujący otrzymuje nadzorcę podczas budowy (nie po słowie). Gwarantuje to, że nadzorca kontroli jest w pełni skonstruowany przed kontrolą. Thuse destruktor zostanie wywołany przed destruktorem supervisora ​​kontroli.

class CS 
{ 
    public: 
     static CS& getInstance() 
     { 
      static CS instance; 
      return instance; 
     } 
     void doregister(C const&); 
     void unregister(C const&); 
    private: 
     CS() 
     { // initialised 
     } 
     CS(CS const&);    // DO NOT IMPLEMENT 
     void operator=(CS const&); // DO NOT IMPLEMENT 
}; 

class C 
{ 
     public: 
      C() 
      { 
       CS::getInstance().doregister(*this); 
      } 
      ~C() 
      { 
       CS::getInstance().unregister(*this); 
      } 
}; 
+0

Myślę, że powinieneś unikać pojedynczego wzorca tutaj, możesz nie wiedzieć, czy kiedykolwiek będziesz potrzebować innej grupy kontrolerów do obsłużenia przez innego przełożonego. – Null303

+0

@ Null303 - w takim przypadku prawdopodobnie nie chciałbyś, aby twoje kontrolery były statyczne/globalne, więc problem z niemożnością kontrolowania życia kontrolera zniknie. –

+0

@ Null303: Jeśli wiesz, że kontrola może mieć inny CS, to musi zostać wstrzyknięta do Kontroli w czasie tworzenia. Oznacza to, że możesz potencjalnie przekazać go jako parametr do konstruktora, co oznacza, że ​​został już utworzony (a więc cały problem znika i tak). –

5

Istnieje zasadniczo cały rozdział na ten temat w Modern Design C++ w Alexandrescu (Chaper 6, Singletons). Definiuje pojedynczą klasę, która może zarządzać zależnościami, nawet między samymi singletonami.

Cała książka jest wysoce zalecana zbyt BTW.

+0

Singletons Phoenix na przykład ... –

+0

Książka została zamówiona :-) – Steven

0

Co powiesz na to, aby opiekun zajął się niszczeniem kontrolerów?

0

OK, jak zasugerowano w innym miejscu, spraw, aby osoba nadzorująca była singletonem (lub obiektem o podobnej kontroli, tj. Z zakresem do sesji).

W razie potrzeby użyj odpowiednich osłon (theading, etc) wokół singleton.

// -- client code -- 
class ControllerClient { 
public: 
    ControllerClient() : 
     controller_(NULL) 
     { 
      controller_ = Controller::create(); 
     } 

    ~ControllerClient() { 
     delete controller_; 
    } 
    Controller* controller_; 
}; 

// -- library code -- 
class Supervisor { 
public: 
    static Supervisor& getIt() {   
     if (!theSupervisor) { 
      theSupervisor = Supervisor(); 
     } 
     return *theSupervisor; 
    } 

    void deregister(Controller& controller) { 
     remove(controller); 
     if(controllers_.empty()) { 
      theSupervisor = NULL; 
      delete this; 
     }  
    } 

private:  
    Supervisor() {} 

    vector<Controller*> controllers_; 

    static Supervisor* theSupervisor; 
}; 

class Controller { 
public: 
    static Controller* create() { 
     return new Controller(Supervisor::getIt()); 
    } 

    ~Controller() { 
     supervisor_->deregister(*this); 
     supervisor_ = NULL; 
    } 
private:  
    Controller(Supervisor& supervisor) : 
     supervisor_(&supervisor) 
     {} 
} 
0

Podczas brzydkie to może być najprostsza metoda:

wystarczy umieścić zaczep try wokół rozmowy wyrejestrować. Nie musisz zmieniać wielu kodów, a ponieważ aplikacja jest już zamykana, nie jest to wielka sprawa. (Czy są też inne ratyfikacje dla porządku zamykania?)

Inni wskazali na lepsze projekty, ale ten jest prosty. (i brzydki)

W tym przypadku preferuję również wzorzec obserwatora.

+0

Cóż, w tym przypadku aplikacja wyłącza się dobrze, zwł. ponieważ pamięć należąca do superwizora jest nadal w pobliżu, a kod sprawdzania błędów uniemożliwia coś naprawdę złego. Więc masz rację, nie ma rzeczywistego problemu, ale obawiam się, że może kiedyś być i wolę to teraz naprawić;) – Steven

+0

To dobry wybór - po prostu oferowanie możliwości. Powiadomienia między sobą są słuszne. Kiedy kontroler otrzymuje powiadomienie, że przełożony odejdzie, może ustawić odwołanie na wartość null i nie informować o tym, aby sam się zniszczył.(jak powiedzieli inni) – Tim

1

GNU gcc/g ++ dostarcza nieprzenośnych atrybutów dla typów, które są bardzo użyteczne. Jednym z tych atrybutów jest init_priority, który definiuje kolejność, w jakiej konstruowane są obiekty globalne, aw konsekwencji kolejność odwrotną, w której są niszczone. Od mężczyzny:

init_priority (priorytet)

In Standard C++, objects defined at namespace scope are guaranteed 
to be initialized in an order in strict accordance with that of 
their definitions _in a given translation unit_. No guarantee is 
made for initializations across translation units. However, GNU 
C++ allows users to control the order of initialization of objects 
defined at namespace scope with the init_priority attribute by 
specifying a relative PRIORITY, a constant integral expression 
currently bounded between 101 and 65535 inclusive. Lower numbers 
indicate a higher priority. 

In the following example, `A' would normally be created before 
`B', but the `init_priority' attribute has reversed that order: 

     Some_Class A __attribute__ ((init_priority (2000))); 
     Some_Class B __attribute__ ((init_priority (543))); 

Note that the particular values of PRIORITY do not matter; only 
their relative ordering. 
0

można użyć Wydarzenia sygnalizować zniszczenie kontrolerów

Dodaj WaitForMultipleObjects w destruktora Inspektora, który będzie czekać, aż wszystkie kontrolery są niszczone .

W destruktorze kontrolerów można podnieść zdarzenie wyjścia kontrolera.

Potrzebujesz obsługi globalnej tablicy zdarzeń zdarzeń Exit dla każdego kontrolera.

+0

to działa, ale zakłada, że ​​aplikacja jest gwintowana, a także jest nieco bardziej skomplikowana niż rozwiązanie obserwatora. – Tim

+0

W rzeczywistości jest to wątek, ale zgadzam się, że jest to trochę przesada, a ponadto nie dotyczy wyłącznie systemu operacyjnego Windows :-) – Steven

0

Standard C++ określa kolejność inicjowania/niszczenia, gdy wszystkie zmienne będą pasowały do ​​jednego pliku ("jednostka tłumaczeniowa"). Wszystko, co obejmuje więcej niż jeden plik, staje się nieprzenośne.

Dodałbym sugestie, aby superwizor zniszczył każdego kontrolera. Jest to bezpieczne dla wątków w tym sensie, że tylko Supervisor każe każdemu się zniszczyć (nikt nie niszczy się samemu), więc nie ma warunków wyścigowych. Trzeba też unikać wszelkich możliwości zakleszczenia (podpowiedź: upewnij się, że kontrolerzy mogą się zniszczyć, gdy ktoś im powie, bez potrzeby, niczego innego, od inspektora).


Jest to możliwe, aby ten wątek bezpieczne, nawet jeśli kontroler musi zostać zniszczona przed końcem programu (czyli jeśli kontrolery mogą być krótkotrwałe) potem (lub ktoś inny).

Po pierwsze, może nie być warunkiem wyścigowym, aby się martwić, jeśli zdecyduję się zniszczyć siebie i mikrosekundy później, Przełożony zdecyduje się zniszczyć mnie i mi powie.

Po drugie, jeśli obawiasz się tego stanu wyścigu, możesz go naprawić, na przykład wymagając, aby wszystkie zgłoszenia zniszczenia przechodziły przez przełożonego. Chcę się zniszczyć. Powiem przełożonemu, żeby mi powiedział lub zarejestrowałam ten zamiar z przełożonym. Jeśli ktoś inny - w tym Przełożony - chce, abym został zniszczony, robi to za pośrednictwem Opiekuna.

+0

Ma to sens, jednak w moim przypadku kontrolery również zostaną zniszczone przed zakończeniem programu (mogą być całkiem krótkotrwały). – Steven

0

Kiedy przeczytałem nagłówek tego pytania, od razu zadałem sobie pytanie: "Jeśli istnieje sposób, że dowolny obiekt może zapewnić, że został zniszczony (zniszczony?) Ostatni, to co by się stało, gdyby dwa obiekty przyjęły tę metodę?"

Powiązane problemy