Singleton static und lazy creation
-
Hallo Leuts,
Eine kurze Frage zu statischen lokalen Variablen.
in einer Funktion:
type &singleton()
{
static type instance;
return instance;
}Und wenn ich schon dabei bin. Gibt es eine gängige/anerkannte Übersetzung für "lazy creation"?
Wird der Speicher für die Variable erst reserviert wenn die Funktion das erste mal aufgerufen wird oder verhält sie sich wie eine globale Variable? Oder anders ausgedrückt. Handelt es sich hierbei um "lazy creation"?
Danke,
Martin
-
Reserviert wird der Speicher im Prinzip schon beim Laden der Binary. Konstruiert wird das Objekt aber erst beim ersten Aufruf der Funktion, außer es handelt sich z.B. um einen primitiven Datentyp...
-
Ich würde das als "lazy initialization" bezeichnen - der Sinn ist aber der selbe.
Deutsche Übersetzung kenne ich keine. Bin aber auch kein Fan von "alles muss übersetzt werden". "Lazy initialization" heisst eben "lazy initialization" - das muss reichen.Ich weiss nicht ob definiert ist, wie der Speicher reserviert wird. Ich kenne aber keine Implementierung wo der Speicher dynamisch reserviert werden würde.
Die Initialisierung findet dann beim ersten Aufruf statt. Das schliesst sowohl den Aufruf des Konstruktors mit ein, als auch die Auswertung diverser Ausdrücke die für die Initialisierung gebraucht werden.
Und zwar unabhängig davon ob der Typ ein eingebauter Typ ala "int" ist oder eine selbst definierte Klasse. Beispiel:#include <time.h> #include <iostream> void foo() { static time_t t1 = time(0); std::cout << t1 << "\n"; } int main() { time_t t0 = time(0); std::cout << t0 << "\n"; sleep(2); // Windows: Sleep(2000); foo(); }
-
hustbaer schrieb:
Ich kenne aber keine Implementierung wo der Speicher dynamisch reserviert werden würde.
Also ich würde da, wenn ich es nicht explizit analysieren würde, keinen Unterschied merken. Deswegen frage ich mich gerade: Woher weißt du sowas? :xmas1:
Gruß,
XSpille
-
Martin Kalbfuß schrieb:
Hallo Leuts,
type &singleton()
{
static type instance;
return instance;
}Handelt es sich hierbei um "lazy creation"?
Nein handelt es nicht, sondern so:
/// Based on Andrei Alexandrescu's idea from the book "Modern C++ Design" class CLazyObject { protected: /** * Need to be friend with UserClassT */ template <class UserClassT> static void create( UserClassT*& _ptr ) { _ptr = new UserClassT; static boost::scoped_ptr<UserClassT> destroyer(_ptr); } }; /// template < class UserClassT, class Lock=CDummyMutex, class InstancePolicy=CLazyObject > class CSingletonT : private InstancePolicy, private boost::noncopyable { public: typedef InstancePolicy InstantiationPolicyT; typedef Lock LockT; /// \brief Access to instance. /// /// Provide access to the single instance through double-checked locking /// /// \return T* single instance /// static UserClassT* instance() { // Uses local static storage to avoid static construction // sequence issues. (regaring when the lock is created) static UserClassT* ptr = 0; static LockT lock; if(!ptr) { lock.lock(); if(!ptr) InstancePolicy::create(ptr); lock.unlock(); } return const_cast<UserClassT*>(ptr); } };Hab ich jetzt mal aus unserer Game-Engine kopiert.
Alternativ gibts die Version von Alexandrescu.Die Instanziierung via Template-Policy erlaubt beliebige Erzeugungsroutinen einzubinden.
Für den normalen Hausgebrauch nutzt man meistens:if(!ptr) ptr = new UserClassT;
-
@nurf: Dein Code ist nicht Exception-sicher
if(!ptr) { lock.lock(); if(!ptr) InstancePolicy::create(ptr); lock.unlock(); }Wenn InstancePolicy::create() eine exception wirft, ist das lock gesetzt und wird nie wieder freigegeben. Besser wäre es ein Lock(lock) (s. Alexandrescu) zu verwenden, dass das lock im Destruktor frei gibt.
-
-
nurf schrieb:
Martin Kalbfuß schrieb:
Hallo Leuts,
type &singleton()
{
static type instance;
return instance;
}Handelt es sich hierbei um "lazy creation"?
Nein handelt es nicht
Ich behaupte weiterhin das Gegenteil.
"instance" wird hier erst erzeugt, wenn singleton() das erste mal aufgerufen wird. Ich nenne das lazy creation bzw. eben lazy initialization.
Wenn du meinst das wäre nicht so, argumentiere es bitte.
-
hustbaer schrieb:
Ich behaupte weiterhin das Gegenteil.
"instance" wird hier erst erzeugt, wenn singleton() das erste mal aufgerufen wird. Ich nenne das lazy creation bzw. eben lazy initialization.
Sehe ich auch so...
-
im alexandrescu wird das ausführlich diskutiert, wenn gewünscht suche ich gleich die seite raus und zitiere das mal, aber hier ist der sinn den er beschreibt
statische variablen werden am anfang des programms erzeugt, aber erst bei der ersten benutzung initialisiert. das heisst der compiler reserviert schon den speicher und teilt den zu, aber bereit für die nutzung ist es erst wenn es laut quellcode erzuegt wird, sprich eben in der zeile "static Singleton singleton;"
und da gibts auch probleme beim initialisieren, wenn nämlich mehrere singletons unternander abhängig sind und sich gegenseitig brauchen zum initialisieren. da bei statischen variablen die initialisierungs reihenfolge nicht festgelegt ist...
-
Also wann der Speicher reserviert wird, spielt aus meiner Sicht mal keine Rolle.
Und Probleme mit der Initialisierungsreihenfolge bekommt man mit function-static Variablen nicht mehr oder weniger als mit der static Pointer + new Methode.
-
type& singleton() { static std::unique_ptr<type> instance(new type()); return *instance; }Und schon wird der Speicher dynamisch reserviert, wobei ich die statische Allokation der ursprünglichen Variante eigentlich eher als Vorteil denn als Nachteil sehen würde.
Ich würd das Ding übrigens nicht unbedingt singleton nennen, denn am Ende verwechselt es noch einer mit dem Singleton Pattern. Auch ist es natürlich von den üblichen Nachteilen globaler Variablen betroffen, weshalb ich von sowas grundsätzlich eher abraten würde, ganz egal wie die konkrete Implementierung am Ende auch aussehen mag...
-
Super Danke für die hilfreichen Antworten. Ich habe es eigentlich nur Singleton genannt, weil mir kein besserer Name eingefallen ist. Gibt es denn einen?
-
Lazy Creation scheint normal zu sein.
Input:
#include <string> #include <cstdio> void test() { static std::string foo = "foo"; foo += "o"; puts(foo.c_str()); } int main() { for(int i = 0; i < 10; ++i) test(); }GCC macht daraus:
0x08048784 <+0>: push %ebp 0x08048785 <+1>: mov %esp,%ebp 0x08048787 <+3>: push %esi 0x08048788 <+4>: push %ebx 0x08048789 <+5>: sub $0x20,%esp 0x0804878c <+8>: mov $0x804a050,%eax 0x08048791 <+13>: movzbl (%eax),%eax 0x08048794 <+16>: test %al,%al 0x08048796 <+18>: jne 0x804880c <_Z4testv+136> 0x08048798 <+20>: movl $0x804a050,(%esp) 0x0804879f <+27>: call 0x8048600 <__cxa_guard_acquire@plt> 0x080487a4 <+32>: test %eax,%eax 0x080487a6 <+34>: setne %al 0x080487a9 <+37>: test %al,%al 0x080487ab <+39>: je 0x804880c <_Z4testv+136> 0x080487ad <+41>: mov $0x0,%ebx 0x080487b2 <+46>: lea -0x9(%ebp),%eax 0x080487b5 <+49>: mov %eax,(%esp) 0x080487b8 <+52>: call 0x80486a0 <_ZNSaIcEC1Ev@plt> 0x080487bd <+57>: lea -0x9(%ebp),%eax 0x080487c0 <+60>: mov %eax,0x8(%esp) 0x080487c4 <+64>: movl $0x8048970,0x4(%esp) 0x080487cc <+72>: movl $0x804a058,(%esp) 0x080487d3 <+79>: call 0x8048660 <_ZNSsC1EPKcRKSaIcE@plt> 0x080487d8 <+84>: movl $0x804a050,(%esp) 0x080487df <+91>: call 0x8048650 <__cxa_guard_release@plt> 0x080487e4 <+96>: mov $0x8048640,%eax 0x080487e9 <+101>: movl $0x804a040,0x8(%esp) 0x080487f1 <+109>: movl $0x804a058,0x4(%esp) 0x080487f9 <+117>: mov %eax,(%esp) 0x080487fc <+120>: call 0x80485f0 <__cxa_atexit@plt> 0x08048801 <+125>: lea -0x9(%ebp),%eax 0x08048804 <+128>: mov %eax,(%esp) 0x08048807 <+131>: call 0x8048670 <_ZNSaIcED1Ev@plt> 0x0804880c <+136>: movl $0x8048974,0x4(%esp) 0x08048814 <+144>: movl $0x804a058,(%esp) 0x0804881b <+151>: call 0x80485e0 <_ZNSspLEPKc@plt> 0x08048820 <+156>: movl $0x804a058,(%esp) 0x08048827 <+163>: call 0x8048620 <_ZNKSs5c_strEv@plt> 0x0804882c <+168>: mov %eax,(%esp) 0x0804882f <+171>: call 0x8048680 <puts@plt> 0x08048834 <+176>: add $0x20,%esp 0x08048837 <+179>: pop %ebx 0x08048838 <+180>: pop %esi 0x08048839 <+181>: pop %ebp 0x0804883a <+182>: ret 0x0804883b <+183>: mov %eax,%esi 0x0804883d <+185>: lea -0x9(%ebp),%eax 0x08048840 <+188>: mov %eax,(%esp) 0x08048843 <+191>: call 0x8048670 <_ZNSaIcED1Ev@plt> 0x08048848 <+196>: test %bl,%bl 0x0804884a <+198>: jne 0x8048858 <_Z4testv+212> 0x0804884c <+200>: movl $0x804a050,(%esp) 0x08048853 <+207>: call 0x8048690 <__cxa_guard_abort@plt> 0x08048858 <+212>: mov %esi,%eax 0x0804885a <+214>: mov %eax,(%esp) 0x0804885d <+217>: call 0x80486c0 <_Unwind_Resume@plt>Was frei von IDA Pro + Hex Rays nach C konvertiert so aussieht:
int __cdecl test() { const char *v0; // eax@4 char v2; // [sp+1Fh] [bp-9h]@3 if ( !(_BYTE)_guard_variable_for_test_void_::foo && __cxa_guard_acquire(&_guard_variable_for_test_void_::foo) != 0 ) { std::allocator<char>::allocator(&v2); std::string::string(&test_void_::foo, &unk_8048970, &v2); __cxa_guard_release(&_guard_variable_for_test_void_::foo); __cxa_atexit(std::string::_string, &test_void_::foo, &_dso_handle); std::allocator<char>::_allocator(&v2); } std::string::operator__(&test_void_::foo, &unk_8048974); v0 = (const char *)std::string::c_str(&test_void_::foo); return puts(v0); }
-
@Ethon
Ich verstehe nicht ganz was du meinst bzw. uns sagen willst.Lazy-Creation im Sinne von "Konstruktor wird erst beim ersten Funktionsaufruf ausgeführt" ist bei function-statics nicht nur normal, sondern vom Standard vorgeschrieben.
Was die Speicher-Allokation für das Objekt selbst angeht, so ist diese in dem von dir gezeigten Code statisch: der "this" Pointer des
std::stringist im nach (fast-) C zurückübersetzten Code&test_void_::foo, also statisch. Das Speicher-Besorgen selbst ist als nicht "lazy".
-
dot schrieb:
type& singleton() { static std::unique_ptr<type> instance(new type()); return *instance; }[...]
Ich würd das Ding übrigens nicht unbedingt singleton nennen, denn am Ende verwechselt es noch einer mit dem Singleton Pattern.Das ist das Singleton-Pattern.
Bzw. so knapp dran, dass ich keinen nennenswerten Unterschied sehe.
Das einzige was hier nicht dem Singleton-Pattern entspricht (bzw. vielleicht - wir sehen zu wenig Code um das sicher sagen zu können) ist, dass die Klasse sich nicht selbst "schützt" und zum Singleton macht.Was Vor-/Nachteile angeht ist das IMO aber nicht wirklich wichtig.
-
hustbaer schrieb:
dot schrieb:
type& singleton() { static std::unique_ptr<type> instance(new type()); return *instance; }[...]
Ich würd das Ding übrigens nicht unbedingt singleton nennen, denn am Ende verwechselt es noch einer mit dem Singleton Pattern.Das ist das Singleton-Pattern.
Bzw. so knapp dran, dass ich keinen nennenswerten Unterschied sehe.
Das einzige was hier nicht dem Singleton-Pattern entspricht (bzw. vielleicht - wir sehen zu wenig Code um das sicher sagen zu können) ist, dass die Klasse sich nicht selbst "schützt" und zum Singleton macht.Die Tatsache dass der Typ gegen Mehfachinstanzierung geschützt wird, ist doch die eine Eigenschaft die ein Singleton ausmacht!? Alles andere (globaler Zugriff auf die Instanz) ist nur Konsequenz einer konkreten Inkarnation des Pattern, nicht aber der Zweck des Singleton Pattern. Das da oben ist in etwa so orthogonal zum Singleton Pattern wie eine globale Variable...
EDIT: Natürlich gehe ich davon aus dass der Typ nicht geschützt ist. Du hast recht dass man das, basierend auf dem Codeschnipsel, nicht gesichert sagen kann, es ist aber imo ziemlich sicher anzunehmen

-
hustbaer schrieb:
Was die Speicher-Allokation für das Objekt selbst angeht, so ist diese in dem von dir gezeigten Code statisch: der "this" Pointer des
std::stringist im nach (fast-) C zurückübersetzten Code&test_void_::foo, also statisch. Das Speicher-Besorgen selbst ist als nicht "lazy".Natürlich landet das in .data/.rdata, anders wäre das ja auch unmöglich umzusetzen.
-
@Ethon/Hustbaer:
Im von Ethon geposteten assembler listing sind ja schon Mutexe für die Konstruktion drin.Macht das speziell nur der gcc oder gilt das z.B. auch bei VC++ (im multithreaded Modell)?
-
Naja, nur GCC will ich jetzt nicht sagen, gibt ja viele Compiler.
MSVC bis inklusive 2010 macht es allerdings nicht.
