Autor Wątek: Singleton - co z tym jest nie tak  (Przeczytany 3351 razy)

Offline yarpen

  • Użytkownik

  • +1
# Marzec 12, 2012, 21:36:14
Destruktor to f-kcja jak kazda inna, jezeli nie jest prywatna, to mozna ja wywolac.

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline Kos

  • Użytkownik
    • kos.gd

# Marzec 12, 2012, 21:40:45
Byle nie dwa razy

Offline WielkiPan

  • Użytkownik

# Marzec 12, 2012, 22:05:25
Destruktor to f-kcja jak kazda inna, jezeli nie jest prywatna, to mozna ja wywolac.
Tak, prawda. Cóż, nigdy tego nie robiłem, bo jakoś zakodował mi się styl z książek i wykładów, że tego nigdy się jawnie nie robi (w kodach źródłowych które czytałem też tego nie robili), nie spotkałem się z tym. Dlatego przepraszam za wprowadzenie w błąd.

Offline tomaszwir

  • Użytkownik

# Marzec 12, 2012, 22:51:16
Byle nie dwa razy
A co to spowoduje ? Usuwanie obiektu o adresie 0 chyba nie spowoduje błędu ?

Jest teraz taki mały problem, rozwiązanie tej sprawy z singletonem rozumiem ale nadal nie łapię idei problemu.
Jak to jest z tymi zależnościami, chodzi o to kiedy obiekt zostanie utworzony ? A co to za różnica ? Bo nie bardzo rozumiem....

Czyli tak jak napisał TDM w rozwiązaniu z wskaźnikiem, spokojnie mogę dać delete w destruktorze, i problem z głowy ? Bo już chciałem pisać statyczny wektor który będzie przechowywał wszystkie adresy obiektów singletona i zwalniaj je jednym wywołaniem metody

Offline ismu

  • Użytkownik

# Marzec 12, 2012, 23:20:18
Hmm mi coś cały czas nie pasuje... Dla uściślenia mamy kod:
template <class Type>
class Singleton
{
private:
      static Type* m_instance;

public:
     static Type& GetInstance()
     {
            if(!m_instance)
                 m_instance = new Type;
            return m_instance;     
     }

     ~Singleton()
     {
            if(m_instance)
            delete m_instance;
     }
};
Twierdzicie, że taka implementacja rozwiązuje problem? To ja się pytam po raz 2 co uruchomi ten destruktor? Powtórze jeszcze raz, instancje tworzone przez new trzeba usunąć za pomocą delete, program sam tego nie zrobi z tego co ja wiem. Możliwe, że jestem w błędzie jeśli tak proszę o logiczne wyjaśnienie:)
A co to spowoduje ? Usuwanie obiektu o adresie 0 chyba nie spowoduje błędu ?

Jest teraz taki mały problem, rozwiązanie tej sprawy z singletonem rozumiem ale nadal nie łapię idei problemu.
Jak to jest z tymi zależnościami, chodzi o to kiedy obiekt zostanie utworzony ? A co to za różnica ? Bo nie bardzo rozumiem....

Czyli tak jak napisał TDM w rozwiązaniu z wskaźnikiem, spokojnie mogę dać delete w destruktorze, i problem z głowy ? Bo już chciałem pisać statyczny wektor który będzie przechowywał wszystkie adresy obiektów singletona i zwalniaj je jednym wywołaniem metody
Kos pewnie miał na myśli przypadek w którym w destruktorze singletona jest delete i w funkcji DeleteInstance() też jest delete. Po wywołaniu delete w DeleteInstance() automatycznie wywoła się destruktor i po raz 2 delete tego "samego miejsca w pamięci" czyli pierwszy delete kasuje a 2 chce skasować tego czego już tak naprawdę nie ma (program dostanie crasha). Instancja jest alokowana w momencie pierwszego użycia GetInstance() później jest tylko zwracana ta instancja. Różnica jest taka że jeśli singleton ma duże rozmiary (w sensie przechowywanych danych, a singletony potrafią takie być mając na uwadze silniki) to jeśli go w ogóle nie użyjesz to zaoszczędzisz pamięci. Po co ci wektor jakiś? Przecież jak będziesz chciał jakiś singleton stworzyć to robisz sobie klasę dziedziczącą po Singleton<typ klasy który ma być singletonem>. Kasujesz go robiąc nazwa_klasy::GetInstance().DeleteInstance(). Każdy singleton powinien być niezależny. Najlepiej jeszcze zdefiniować sobie jakieś makro które zastąpi singleton::GetInstance(), ładniej to w kodzie wygląda według mnie ;)
« Ostatnia zmiana: Marzec 13, 2012, 00:17:53 wysłana przez ismu »

Offline lgromanowski

  • Użytkownik
    • OpenMW, Elderscrolls III: Morrowind engine reimplementation

# Marzec 12, 2012, 23:39:37
Hmm mi coś cały czas nie pasuje... Dla uściślenia mamy kod:
template <class Type>
class Singleton
{
private:
      static Type* m_instance;

public:
     Type& GetInstance()
     {
            if(!m_instance)
                 m_instance = new Type;
            return m_instance;     
     }

     ~Singleton()
     {
            delete m_instance;
     }
};
program sam tego nie zrobi z tego co ja wiem. Możliwe, że jestem w błędzie jeśli tak proszę o logiczne wyjaśnienie:)

#include <cstdlib>
#include <mutex>

template <class T>
struct Singleton {
  Singleton(Singleton&) = delete;
  Singleton& operator=(Singleton&) = delete;

  static T* getInstance() {
    if (m_instance == nullptr) {
      std::lock_guard<std::mutex> lock(m_mutex);
      if (m_instance == nullptr) {
        m_instance = new T;
        atexit(destroy);
      }
    }

    return m_instance;
  }

  protected:
    Singleton(){}
    virtual ~Singleton(){}

    static void destroy() {
      delete m_instance;
    }

    static T* m_instance;
    static std::mutex m_mutex;
};

template <class T>
T* Singleton<T>::m_instance = nullptr;

template <class T>
std::mutex Singleton<T>::m_mutex;

struct A : public Singleton<A> {
};


int main() {
  A::getInstance();
  return 0;
}

Offline ismu

  • Użytkownik

# Marzec 12, 2012, 23:53:33
No to teraz pojechałeś z tym atexit() :) Owszem takie rozwiązanie przejdzie, ale wydaje mi się że angażowanie w to wszystko atexit() to przerost formy nad treścią ;p Ja zazwyczaj staram się unikać tak "wyszukanych" rozwiązań bo według mnie wprowadza się zamieszanie w kod i dochodzi ta obsługa wątków... Ja staram się uświadomić tylko, że nic nie jest wstanie uruchomić destruktora(prócz delete, jawnego wywołania, atexit()) instancji tworzonej za pomocą new(możliwe że się mylę ale raczej nie). Tym samym wrzucenie delete do destruktora nie rozwiązuje problemu kasowania instancji automatycznie.
« Ostatnia zmiana: Marzec 13, 2012, 17:47:35 wysłana przez ismu »

Offline tomaszwir

  • Użytkownik

# Marzec 13, 2012, 00:12:12
wyjaśnienie:)Kos pewnie miał na myśli przypadek w którym w destruktorze singletona jest delete i w funkcji DeleteInstance() też jest delete. Po wywołaniu delete w DeleteInstance() automatycznie wywoła się destruktor i po raz 2 delete tego "samego miejsca w pamięci" czyli pierwszy delete kasuje a 2 chce skasować tego czego już tak naprawdę nie ma (program dostanie crasha).
Np chyba że przed delete jest if sprawdzający czy wskaźnik jest różny od 0.

Instancja jest alokowana w momencie pierwszego użycia GetInstance() później jest tylko zwracana ta instancja. Różnica jest taka że jeśli singleton ma duże rozmiary (w sensie przechowywanych danych, a singletony potrafią takie być mając na uwadze silniki) to jeśli go w ogóle nie użyjesz to zaoszczędzisz pamięci.
Dalej nie rozumiem dlaczego wersja z wskaźikiem jest lepsza od tej z referencją...

Po co ci wektor jakiś? Przecież jak będziesz chciał jakiś singleton stworzyć to robisz sobie klasę dziedziczącą po Singleton<typ klasy który ma być singletonem>. Kasujesz go robiąc nazwa_klasy::GetInstance().DeleteInstance()
Po to żeby nie martwić się o pamiętanie żeby usunąć utworzony singleton, chodziło mi o takie coś:
template <typename T>
class CSingleton
{
public:
         T* getPtr()
         {
             if(cClassPointer == 0)
             {
                    cClassPointer = new T;
           
                    cSingletonPtr.push_back(cClassPointer);
             }
             return cClassPointer;
          }

         static void releaseSingletons()
         {
                  for(int i = 0; i < cSingletonPtr.size(); i++)
                        delete cSingletonPtr[i];
         }
private:
         static T* cClassPointer;

         static std::vector<CSingleton*> cSingletonPtr;
}
Tak w ogóle to tej wersji to nie ma chyba różnicy czy wskaźnik pokazujący na klasę (cClassPointer) jest statyczny, przecież i tak ta klasa będzie tylko dziedziczona

Offline ismu

  • Użytkownik

# Marzec 13, 2012, 00:25:06
Tak w ogóle to tej wersji to nie ma chyba różnicy czy wskaźnik pokazujący na klasę (cClassPointer) jest statyczny, przecież i tak ta klasa będzie tylko dziedziczona
Oj chyba ma znaczenie. Tak jak sam powiedziałeś Singleton będzie tylko dziedziczony. Chcąc odwołać się do singletona robisz classa_singleton::GetInstance() wiec tak tylko możesz zrobić jak GetInstance() jest statyczna metodą. Co do deklaracji wskaźnika jako static wtedy gwarantujesz że jak nawet zrobisz sobie 2 singletony tego samego typu to dostaniesz tą samą instancje.
« Ostatnia zmiana: Marzec 13, 2012, 00:27:13 wysłana przez ismu »

Offline tomaszwir

  • Użytkownik

# Marzec 13, 2012, 00:26:23
fakt, porypało mi się coś....

Offline Xion

  • Redaktor
    • xion.log

# Marzec 13, 2012, 11:14:53
Cytuj
No to teraz pojechałeś z tym atexit() :) Owszem takie rozwiązanie przejdzie, ale wydaje mi się że angażowanie w to wszystko atexit() to przerost formy nad treścią ;p Ja zazwyczaj staram się unikać tak "wyszukanych" rozwiązań bo według mnie wprowadza się zamieszanie w kod (...)
Aha... A dziwny destruktor który wywołuje się nie wiadomo jak i nie wiadomo przez kogo to oczywiście jest zupełnie intuicyjne i poprawne rozwiązanie, które nie wprowadza zamieszania w kodzie? Ewentualnie - trzymając się raczej rozwiązań które mają szansę działać - jawne zwalnianie singletona (i konieczność pamiętania o tym) też wprowadza mniej zamieszania niż atexit() który robi to automatycznie?...

Cytuj
dochodzi ta obsługa wątków
Rozumiem że jeśli używamy innej implementacji singletona to wszystkie problemy z wielowątkowością magicznie znikają i sychronizacja staje się w ogóle zbędna, bo wszystko po prostu działa? :)

Cytuj
Ja staram się uświadomić tylko, że nic nie jest wstanie uruchomić destruktora(prócz delete i jawnego wywołania) instancji tworzonej za pomocą new(możliwe że się mylę ale raczej nie)
Cały pomysł z wywoływaniem delete w destruktorze jest tu wysoce kuriozalny, bo jedynym obiektem który może zostać zniszczony - a więc wywołać ów destruktor - jest ten właśnie jedyny obiekt który chcemy w ten sposób zniszczyć. Widać tu pewien cyrkularny problem, czyż nie?

Cytuj
Np chyba że przed delete jest if sprawdzający czy wskaźnik jest różny od 0.
delete można bezkarnie wywoływać na wskaźniku pustym więc if jest zbędny. Co oczywiście nie zmienia faktu że cały ten destruktor jest bez sensu - patrz wyżej.

Cytuj
Po to żeby nie martwić się o pamiętanie żeby usunąć utworzony singleton, chodziło mi o takie coś:
o_0 To jakiś konkurs na najbardziej zadziwiający anti-pattern? :)

Okej, a teraz trochę poważniej. Jeśli NAPRAWDĘ nie potrafisz określić czasu życia tego swojego singletona (hint: powinieneś być w stanie to zrobić), to rozwiązanie polygon7 jest chyba jedynym spośród tu podanych które faktycznie miałoby szansę działać. Ma ono jeden błąd związany z niezdefiniowaną kolejnością  inicjalizacji zmiennych globalnych z różnych modułów (który dałoby się naprawić przenosząc m_instance do wewnątrz GetInstance), ale na tle innych prezentowanych tu "rozwiązań" to zaledwie malutki, nic nieznaczący kruczek :)

Offline ismu

  • Użytkownik

# Marzec 13, 2012, 16:59:14
@Xion  Chyba nie czytałeś topica od początku :) Moja implementacja którą podałem jest bez sensu ja to wiem, ale ona miała posłużyć, żeby komuś kto wcześniej pisał uświadomić to, że nawet jak damy delete do destruktora to program przy kończeniu pracy tego destruktora sam nie wywoła(może się mylę?). Wcześniej powstały pomysły typu, że jak klasa nie będzie miała metody DeleteInstance()(w której jest delete) a wrzucimy delete do destruktora to będzie wszystko ok z kasowaniem instancji. Co do atexit() ja osobiście nie przepadam za tym rozwiązaniem wolę samemu wszystko kasować, albo w przypadku singletona ewentualnie zrobić na zasadzie takiej jak jest zrobiony w silniku nGene Riddlmastera(taka sama implementacja jest w Perełkach programowania gier tom 1 tzw. Automatyczny Singleton). Co do wątków w normalnym singletonie jest też to potrzebne, ale jako iż autor wątku nic o tym nie napisał więc uznałem, że używa tylko 1 wątku i darowałem sobie ich obsługę.
« Ostatnia zmiana: Marzec 13, 2012, 17:11:25 wysłana przez ismu »

Offline Veldrin

  • Użytkownik

# Marzec 13, 2012, 19:39:36
A jakie przesłanki stoją za koniecznością sprzątania przed zakończeniem pracy aplikacji?

Offline lgromanowski

  • Użytkownik
    • OpenMW, Elderscrolls III: Morrowind engine reimplementation

# Marzec 13, 2012, 19:44:55
A jakie przesłanki stoją za koniecznością sprzątania przed zakończeniem pracy aplikacji?
Takie, że możesz czasem pisać soft na system, który nie sprząta po aplikacji.

Offline agent_J

  • Użytkownik

# Marzec 13, 2012, 19:47:35
A jakie przesłanki stoją za koniecznością sprzątania przed zakończeniem pracy aplikacji?

Jeśli to jest biblioteka, która może być wielokrotnie inicjalizowana/niszczona w trakcie działania aplikacji, to sprzątanie musi być jak najbardziej. W przeciwnym przypadku można to kompletnie olać.  Wszystkie zasoby będą zwolnione przez jądro systemu przy zamykaniu aplikacji. Jądro ma w odbycie rzeczy typu malloc w userspace - dla jądra istnieje tylko VirtualAlloc/Free/mmap/itp., za pomocą których zasoby utworzone niszczone są przy zamykaniu procesu. Sockety, pliki (uchwyty) - niszczone przez jądro.

Takie, że możesz czasem pisać soft na system, który nie sprząta po aplikacji.

Jak ktoś musi pisać na takie systemy to nie zadaje pytań na temat singletona ;)
« Ostatnia zmiana: Marzec 13, 2012, 19:49:40 wysłana przez agent_J »