Ostatnio natknąłem się na realizację / implementację wzorca projektowego Singleton dla C ++. Wyglądało to tak (przyjąłem z prawdziwego przykładu):
// a lot of methods are omitted here
class Singleton
{
public:
static Singleton* getInstance( );
~Singleton( );
private:
Singleton( );
static Singleton* instance;
};
Z tej deklaracji mogę wywnioskować, że pole instancji jest inicjowane na stercie. Oznacza to, że istnieje przydział pamięci. Co jest dla mnie całkowicie niejasne, kiedy dokładnie pamięć zostanie zwolniona? Czy jest błąd i wyciek pamięci? Wygląda na to, że jest problem z implementacją.
Moje główne pytanie brzmi: jak mam to odpowiednio wdrożyć?
c++
design-patterns
singleton
Artem Barger
źródło
źródło
Odpowiedzi:
W 2008 roku dostarczyłem implementację C ++ 98 wzorca projektowego Singleton, który jest leniwie oceniany, gwarantuje zniszczenie, nie jest technicznie bezpieczny dla wątków:
Czy ktoś może dostarczyć mi próbkę Singletona w c ++?
Oto zaktualizowana implementacja wzoru konstrukcyjnego Singleton w C ++ 11, która jest leniwie oceniana, poprawnie zniszczona i bezpieczna dla wątków .
Przeczytaj ten artykuł o tym, kiedy używać singletona: (niezbyt często)
Singleton: jak go używać
Zobacz dwa artykuły na temat kolejności inicjalizacji i sposobu radzenia sobie z nią:
Kolejność inicjalizacji zmiennych statycznych
Znalezienie problemów z kolejnością inicjalizacji statycznej C ++
Zobacz ten artykuł opisujący czasy życia:
jaki jest czas życia zmiennej statycznej w funkcji C ++?
Zobacz ten artykuł, który omawia niektóre implikacje wątków dla singletonów:
Instancja Singleton zadeklarowana jako zmienna statyczna metody GetInstance, czy jest bezpieczna dla wątków?
Zobacz ten artykuł, który wyjaśnia, dlaczego podwójne sprawdzanie blokowania nie działa w C ++:
Jakie są wszystkie typowe niezdefiniowane zachowania, o których powinien wiedzieć programista C ++?
Dr Dobbs: C ++ i The Perils of Double-Checked Locking: Część I
źródło
What irks me most though is the run-time check of the hidden boolean in getInstance()
Jest to założenie dotyczące techniki wdrażania. Nie trzeba zakładać, że żyje. patrz stackoverflow.com/a/335746/14065 Możesz wymusić sytuację, aby zawsze była żywa (mniej narzutów niżSchwarz counter
). Zmienne globalne mają więcej problemów z kolejnością inicjowania (pomiędzy jednostkami kompilacji), ponieważ nie wymusza się kolejności. Zaletą tego modelu jest 1) leniwa inicjalizacja. 2) Zdolność do egzekwowania rozkazu (Schwarz pomaga, ale jest brzydszy). Tak,get_instance()
jest o wiele brzydszy.Będąc Singletonem, zwykle nie chcesz, aby został zniszczony.
Zostanie zerwany i zwolniony po zakończeniu programu, co jest normalnym, pożądanym zachowaniem dla singletona. Jeśli chcesz mieć możliwość jawnego wyczyszczenia, dość łatwo jest dodać do klasy metodę statyczną, która pozwala przywrócić ją do stanu czystego i umożliwić jej ponowne przydzielenie przy następnym użyciu, ale jest to poza zakresem „klasyczny” singleton.
źródło
Możesz uniknąć przydziału pamięci. Istnieje wiele wariantów, wszystkie mają problemy w przypadku środowiska wielowątkowego.
Wolę ten rodzaj implementacji (właściwie nie jest powiedziane, że wolę, ponieważ unikam singletonów tak bardzo, jak to możliwe):
Nie ma dynamicznego przydziału pamięci.
źródło
Odpowiedź @Loki Astari jest doskonała.
Jednak zdarzają się sytuacje, w których wiele obiektów statycznych musi być w stanie zagwarantować, że singleton nie zostanie zniszczony, dopóki wszystkie statyczne obiekty, które korzystają z singletonu nie będą go już potrzebować.
W takim przypadku
std::shared_ptr
można użyć do utrzymania singletona przy życiu dla wszystkich użytkowników, nawet gdy statyczne destrukatory są wywoływane na końcu programu:źródło
Inna alternatywa nieprzydzielająca: stwórz singletona, powiedzmy, klasę
C
, jak potrzebujesz:za pomocą
Ani to, ani odpowiedź Cătălin nie jest automatycznie wątkowo bezpieczna w obecnym C ++, ale będzie w C ++ 0x.
źródło
Wśród odpowiedzi nie znalazłem implementacji CRTP, więc oto:
Aby użyć, po prostu odziedzicz z tego swoją klasę, na przykład:
class Test : public Singleton<Test>
źródło
Rozwiązanie w przyjętej odpowiedzi ma znaczną wadę - destruktor dla singletonu jest wywoływany po opuszczeniu
main()
funkcji przez kontrolę . Naprawdę mogą pojawić się problemy, gdy w obiekcie zostaną przydzielone niektóre obiekty zależnemain
.Spotkałem ten problem, gdy próbowałem wprowadzić Singleton w aplikacji Qt. Zdecydowałem, że wszystkie moje okna dialogowe instalacji muszą być singletonami, i zastosowałem powyższy wzór. Niestety, główna klasa Qt
QApplication
została przydzielona na stosie wmain
funkcji, a Qt zabrania tworzenia i niszczenia okien dialogowych, gdy żaden obiekt aplikacji nie jest dostępny.Dlatego wolę przydzielone sterty singletonów. Podaję wyraźne
init()
iterm()
metody dla wszystkich singletonów i nazywam je wewnątrzmain
. W ten sposób mam pełną kontrolę nad kolejnością tworzenia / niszczenia singletonów, a także gwarantuję, że singletony zostaną utworzone bez względu na to, czy ktoś zadzwonił,getInstance()
czy nie.źródło
Oto łatwa implementacja.
Utworzono tylko jeden obiekt, a odwołanie do tego obiektu jest zwracane za każdym razem po słowie.
Tutaj 00915CB8 to lokalizacja pamięci obiektu Singleton, taka sama przez czas trwania programu, ale (normalnie!) Różna za każdym razem, gdy program jest uruchamiany.
Uwaga: To nie jest wątek bezpieczny. Musisz zapewnić bezpieczeństwo wątku.
źródło
Jeśli chcesz przydzielić obiekt w stercie, dlaczego nie użyć unikalnego wskaźnika. Pamięć również zostanie zwolniona, ponieważ używamy unikalnego wskaźnika.
źródło
m_s
lokalnystatic
zgetInstance()
i zainicjować go natychmiast bez badania.Prawdopodobnie jest przydzielany ze stosu, ale bez źródeł nie ma możliwości poznania.
Typowa implementacja (zaczerpnięta z kodu, który już mam w emacsie) to:
... i polegać na tym, że program wykracza poza zakres, aby potem posprzątać.
Jeśli pracujesz na platformie, na której czyszczenie musi być wykonane ręcznie, prawdopodobnie dodam procedurę czyszczenia ręcznego.
Innym problemem związanym z robieniem tego w ten sposób jest to, że nie jest bezpieczny dla wątków. W środowisku wielowątkowym dwa wątki mogłyby przedostać się przez „if”, zanim którekolwiek z nich będzie miało szansę na przydzielenie nowej instancji (tak by oba miały). To nadal nie jest zbyt duża sprawa, jeśli i tak chcesz zakończyć program.
źródło
Czy ktoś wspomniał
std::call_once
istd::once_flag
? Większość innych podejść - w tym podwójnie sprawdzone ryglowanie - jest zepsuta.Jednym z głównych problemów we wdrażaniu wzorca singletonu jest bezpieczna inicjalizacja. Jedynym bezpiecznym sposobem jest ochrona sekwencji inicjalizacji barierami synchronizującymi. Ale same bariery muszą zostać bezpiecznie zainicjowane.
std::once_flag
to mechanizm gwarantujący bezpieczną inicjalizację.źródło
Ostatnio omówiliśmy ten temat w mojej klasie EECS. Jeśli chcesz szczegółowo zapoznać się z notatkami z wykładu, odwiedź http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf
Są dwa sposoby, które znam, aby poprawnie utworzyć klasę Singleton.
Pierwszy sposób:
Zaimplementuj to w sposób podobny do tego w przykładzie. Jeśli chodzi o zniszczenie, „Singletony zwykle znoszą czas trwania programu; większość systemów operacyjnych odzyska pamięć i większość innych zasobów po zakończeniu programu, więc istnieje argument za tym, aby się tym nie martwić”.
Jednak dobrą praktyką jest sprzątanie po zakończeniu programu. Dlatego możesz to zrobić za pomocą pomocniczej statycznej klasy SingletonDestructor i zadeklarować to jako przyjaciela w swoim Singleton.
Singleton_destroyer zostanie utworzony podczas uruchamiania programu, a „gdy program się zakończy, wszystkie globalne / statyczne obiekty zostaną zniszczone przez kod zamknięcia biblioteki wykonawczej (wstawiony przez linker), więc narzędzie_destroyer zostanie zniszczone; jego destruktor usunie Singleton, uruchamiając jego burzyciel."
Drugi sposób
Nazywa się to Meyers Singleton, stworzony przez C ++ Wizard Scott Meyers. Po prostu zdefiniuj get_instance () inaczej. Teraz możesz także pozbyć się zmiennej elementu wskaźnika.
Jest to miłe, ponieważ zwracana wartość jest referencyjna i można użyć
.
składni zamiast->
dostępu do zmiennych składowych.„Kompilator automatycznie buduje kod, który po raz pierwszy tworzy„ s ”poprzez deklarację, a nie później, a następnie usuwa obiekt statyczny po zakończeniu programu.”
Zauważ również, że dzięki Meyers Singleton „możesz dostać się w bardzo trudną sytuację, jeśli obiekty polegają na sobie w momencie zakończenia - kiedy Singleton znika w stosunku do innych obiektów? Ale w przypadku prostych aplikacji działa to dobrze”.
źródło
Oprócz innych dyskusji tutaj warto zauważyć, że możesz mieć globalność bez ograniczania użycia do jednej instancji. Rozważmy na przykład przypadek liczenia czegoś ...
Teraz gdzieś wewnątrz funkcji (np.
main
) Możesz:Referencje nie muszą przechowywać wskaźnika z powrotem do swoich odpowiednich,
Store
ponieważ te informacje są dostarczane w czasie kompilacji. Nie musisz też martwić się oStore
żywotność, ponieważ kompilator wymaga, aby był globalny. Jeśli rzeczywiście jest tylko jeden przypadekStore
, w takim podejściu nie ma kosztów ogólnych; z więcej niż jedną instancją kompilator musi być sprytny w generowaniu kodu. Jeśli to konieczne,ItemRef
klasa może być nawet wykonanafriend
zStore
(możesz mieć szablonów przyjaciół!).Jeśli
Store
sama jest klasą szablonową, robi się bałagan, ale nadal można skorzystać z tej metody, być może poprzez zaimplementowanie klasy pomocniczej o następującej sygnaturze:Użytkownik może teraz utworzyć
StoreWrapper
typ (i instancję globalną) dla każdejStore
instancji globalnej i zawsze uzyskiwać dostęp do sklepów za pośrednictwem instancji opakowania (w ten sposób zapominając o krwawych szczegółach parametrów szablonu potrzebnych do użyciaStore
).źródło
Chodzi o zarządzanie czasem życia obiektu. Załóżmy, że masz więcej niż singletony w swoim oprogramowaniu. I zależą od singletona Logger. Podczas niszczenia aplikacji załóżmy, że inny obiekt singleton używa Loggera do rejestrowania kroków niszczenia. Musisz zagwarantować, że Logger powinien zostać wyczyszczony na końcu. Dlatego proszę również sprawdzić ten artykuł: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf
źródło
Moja implementacja jest podobna do Galika. Różnica polega na tym, że moja implementacja pozwala współużytkowanym wskaźnikom wyczyścić przydzieloną pamięć, w przeciwieństwie do zatrzymywania pamięci do momentu zamknięcia aplikacji i wyczyszczenia wskaźników statycznych.
źródło
Twój kod jest poprawny, z wyjątkiem tego , że nie zadeklarowałeś wskaźnika instancji poza klasą . Wewnętrzne deklaracje klas zmiennych statycznych nie są uważane za deklaracje w C ++, jednak jest to dozwolone w innych językach, takich jak C # lub Java itp.
Musisz wiedzieć, że instancja Singleton nie musi być przez nas ręcznie usuwana . Potrzebujemy jednego obiektu w całym programie, więc pod koniec wykonywania programu zostanie on automatycznie zwolniony.
źródło
Dokument, do którego odsyłano powyżej, opisuje niedociągnięcie podwójnie sprawdzonego blokowania, ponieważ kompilator może przydzielić pamięć dla obiektu i ustawić wskaźnik na adres przydzielonej pamięci przed wywołaniem konstruktora obiektu. W c ++ jest jednak dość łatwe użycie ręcznych alokatorów do ręcznego przydzielenia pamięci, a następnie wywołanie konstrukcyjne w celu zainicjowania pamięci. Przy użyciu tej metody sprawdzanie blokady działa dobrze.
źródło
Przykład:
źródło
Prosta klasa singletona. Musi to być plik klasy nagłówka
Uzyskaj dostęp do singletona w ten sposób:
źródło
Myślę, że powinieneś napisać funkcję statyczną, w której twój obiekt statyczny zostanie usunięty. Powinieneś wywołać tę funkcję, gdy masz zamiar zamknąć aplikację. Zapewni to, że nie masz wycieku pamięci.
źródło