Hallo,
ich habe letztens dieses Konstrukt in C++ gesehen und mich gefragt was
das eigentlich genau bewirkt:
1
constDate&default_date()
2
{
3
staticconstDatedd(1970,1,1);// initialize dd first time we get here
4
returndd;
5
}
Der Vorteil soll sein, dass die Variable "dd" nicht bei jedem Aufruf von
neuem erstellt wird, was der Fall wäre wenn man das Schlüsselwort
"static" weglassen würde, was dann speicherintensiver wäre wenn es
zigtausendmal aufgerufen werden würde.
Bleibt eine Variable die mit "static" deklariert wird auch nach dem
Verlassen der Funktion bestehen?
friedelbär schrieb:> Bleibt eine Variable die mit "static" deklariert wird auch nach dem> Verlassen der Funktion bestehen?
Ja, die wird einmal beim Betreten der Funktion angelegt und gilt dann
für immer. Wenn die nicht "const" ist kann das z.B. beim Multithreading
problematisch werden. Zudem muss bei jedem Betreten der Funktion geprüft
werden, ob die Variable bereits initialisiert wurde, was minimal
ineffizienter ist, als die Variable als statische Klassen-Variable oder
globale statische Variable anzulegen.
friedelbär schrieb:> Der Vorteil soll sein, dass die Variable "dd" nicht bei jedem Aufruf von> neuem erstellt wird, was der Fall wäre wenn man das Schlüsselwort> "static" weglassen würde, was dann speicherintensiver wäre wenn es> zigtausendmal aufgerufen werden würde.
Speicherintensiver nicht. Die Variable existiert ja dann jedes mal nur
solange, bis die Funktion beendet wird. Aber die Variable muss für jeden
Aufruf der Funktion eben neu erstellt und nachher wieder zerstört
werden. Wenn sie static ist, muss das nur einmal gemacht werden.
friedelbär schrieb:> Bleibt eine Variable die mit "static" deklariert wird auch nach dem> Verlassen der Funktion bestehen?
Ja. Genau das ist es, was static in einer Funktion bewirkt.
Rolf M. schrieb:>> Bleibt eine Variable die mit "static" deklariert wird auch nach dem>> Verlassen der Funktion bestehen?>> Ja. Genau das ist es, was static in einer Funktion bewirkt.
Super vielen Dank euch!
Markus L. schrieb:> Rolf M. schrieb:>> Speicherintensiver nicht.> Doch. Static Locals belegen permanent Speicher, Non-Static nur dann,> wenn sie im Scope sind.
Und non-static locals können beliebig oft im Speicher liegen bei
rekursivem Funktionsaufruf.
Markus L. schrieb:> Rolf M. schrieb:>> Speicherintensiver nicht.> Doch. Static Locals belegen permanent Speicher, Non-Static nur dann,> wenn sie im Scope sind.
Und warum macht das nicht-statische lokale Variablen speicherintensiver?
mh schrieb:> Und non-static locals können beliebig oft im Speicher liegen bei> rekursivem Funktionsaufruf.
Ja, oder bei Multithreading, aber es ging hier eigentlich nur um den
ganz einfachen Fall, ...
friedelbär schrieb:> wenn es zigtausendmal aufgerufen werden würde.
Dr. Sommer schrieb:> Ja, die wird einmal beim Betreten der Funktion angelegt [...]
Die wird bereits beim Start des Programms angelegt, nicht erst, wenn die
Funktion "betreten" wird. ;-)
Frank M. schrieb:>> Ja, die wird einmal beim Betreten der Funktion angelegt [...]>> Die wird bereits beim Start des Programms angelegt, nicht erst, wenn die> Funktion "betreten" wird. ;-)
Was auch bedeutet, dass der Konstruktur aufgerufen wird, bevor main()
aktiv ist. Das sollte beachtet werden, wenn in main() irgendwelche
Initialisierungen erfolgen, von denen der Konstruktor abhängig ist.
A. K. schrieb:> Frank M. schrieb:>>> Ja, die wird einmal beim Betreten der Funktion angelegt [...]>>>> Die wird bereits beim Start des Programms angelegt, nicht erst, wenn die>> Funktion "betreten" wird. ;-)
Nein.
> Was auch bedeutet, dass der Konstruktur aufgerufen wird, bevor main()> aktiv ist.
Nein.
Das Objekt wird beim ersten Aufruf der Funktion erzeugt. Wird die
Funktion nie aufgerufen, dann gibt es auch das Objekt nicht.
A. K. schrieb:> Was auch bedeutet, dass der Konstruktur aufgerufen wird, bevor main()> aktiv ist. Das sollte beachtet werden, wenn in main() irgendwelche> Initialisierungen erfolgen, von denen der Konstruktor abhängig ist.
Hmm, genau, da kann man in schlimme Probleme durch
Konstruktionsabhängigkeiten kommen sonst. z.B. die übliche
Singleton<>-Implementierung, wo 'ne static-Variable in 'ner Funktion
drinne ist. Schlimm wirds dann, wenn ein Singleton auf ein anderes
zugreift...
Bin daher auch kein großer Fan von lokalen static-Variablen. Zum Einen
wegen der Threadsafety-Geschichte, zum Anderen weil es irgendwo
kontraintuitiv zum OOP-Ansatz ist, 'ne Funktion mit 'nem
Gedächtnis/Zustand auszustatten - ist 'n irgendwie ein CodeSmell. Würde
Zustände lieber durch Member von Objekte erreichen, nicht durch solche
Tricks mit alten C-Sprach-Anleihen.
Just my 2 cents
db8fs schrieb:> Würde Zustände lieber durch Member von Objekte erreichen, nicht durch> solche Tricks mit alten C-Sprach-Anleihen.
Im Gegensatz zu lokalen static-Variblen hat man bei static-Members
tatsächlich die von die oben geschilderten Probleme.
Rolf M. schrieb:> db8fs schrieb:>> Würde Zustände lieber durch Member von Objekte erreichen, nicht durch>> solche Tricks mit alten C-Sprach-Anleihen.>> Im Gegensatz zu lokalen static-Variblen hat man bei static-Members> tatsächlich die von die oben geschilderten Probleme.
Nee, nee, die kann man so auch kriegen.
Angenommen folgende Implementierung (Scott Meyers Singleton):
1
static Singleton& instance()
2
{
3
static Singleton s;
4
return s;
5
}
Das ganze als Klassenfunktion eines Singleton-templates (macht nur
Konstruktor/Copy Constructor + Assignment private) gebaut zu
1
static T & Singleton<T>::instance()
2
{
3
static T s;
4
return s;
5
}
Jetzt instanziiert man einfach zwei verschiedene Singletons mit
Datentypen, die zur Konstruktionszeit voneinander abhängig sind.
Passiert in gewachsenem Code sogar manchmal fast zufällig.
1
Foo & foo( Singleton<Foo>()::instance() );
2
Bar & bar( Singleton<Bar>()::instance() );
Referenziert 'Foo' irgendwie durch 'ne Member 'Bar' über diese
Singleton-Implementierung, kracht es. Bzw. es wird vollkommen
undurchschaubar, wann welches Singleton-Objekt zuerst entsteht.
Daher lieber durch'n Konstruktor die Abhängigkeiten klären und auf
solchen Singleton-Sch**ß verzichten:
Bzw. was ich sagen will, aber irgendwie nicht so richtig rauskommt: das
Konzept "lokal geht vor global" kann durch solchen static-Kram
unterwandert werden, egal nun ob durch Klassenmember oder lokale
static-Variablen.
'ne RaceCondition zur Konstruktionszeit kann man also auch so
hinkriegen, man muss nur eine Resource unnötig global machen.
db8fs schrieb:> Referenziert 'Foo' irgendwie durch 'ne Member 'Bar' über diese> Singleton-Implementierung, kracht es. Bzw. es wird vollkommen> undurchschaubar, wann welches Singleton-Objekt zuerst entsteht.
Was ist das Problem? Bar wird instanziiert, wenn es zum ersten mal
benutzt wird. Wenn Foo Bar braucht, wird es eben genau dort
instanziiert. Wenn du ein Singleton verwendest, sollte dir auch egal
sein, ob es schon existiert oder erst in dem Moment erzeugt werden muss.
Das passiert ja schließlich im Hintergrund.
db8fs schrieb:> Daher lieber durch'n Konstruktor die Abhängigkeiten klären und auf> solchen Singleton-Sch**ß verzichten:>> Bar::Bar( Foo & foo );
Ich sehe nicht, wie das jetzt Singletons vollständig ersetzen soll. Das
ist doch nur ein Konstruktor. Wo, wie und wann die Objekte erzeugt
werden, fehlt noch.
Rolf M. schrieb:> db8fs schrieb:>> Referenziert 'Foo' irgendwie durch 'ne Member 'Bar' über diese>> Singleton-Implementierung, kracht es. Bzw. es wird vollkommen>> undurchschaubar, wann welches Singleton-Objekt zuerst entsteht.>> Was ist das Problem? Bar wird instanziiert, wenn es zum ersten mal> benutzt wird. Wenn Foo Bar braucht, wird es eben genau dort> instanziiert. Wenn du ein Singleton verwendest, sollte dir auch egal> sein, ob es schon existiert oder erst in dem Moment erzeugt werden muss.> Das passiert ja schließlich im Hintergrund.
Hmm, eben das ist halt leider nicht egal, wenn man sicherstellen muss,
dass die Objekte nur innerhalb eines gewissen Threadkontextes existieren
dürfen, z.B. UI-Thread. Hat man nur einen Thread, haste 100% recht.
> Ich sehe nicht, wie das jetzt Singletons vollständig ersetzen soll. Das> ist doch nur ein Konstruktor. Wo, wie und wann die Objekte erzeugt> werden, fehlt noch.
Na ja, nee. Ein Singleton ist immer ein querschnittlicher, globaler
Ansatz durch die gesamte Applikation hindurch. Es ist zudem eine harte
Einschränkung auf eine Klasse, indem höchstens eine Instanz davon
erlaubt wird.
Semantisch äquivalent zur obigen Singleton-Implementierung wäre es ja,
einfach im top-level-Objekt der Applikation (z.B. Klasse 'Plugin')
Instanzen von Foo und Bar anzulegen. Und um Foo anlegen zu können, muss
halt vorher Bar da sein.
Wenns halt nicht anders geht, außer durch globalen Zugriff, muss sich
dann ein Foo bzw. Bar-Referenzierender aus einer anderen Klasse über das
Top-Level-Objekt die Instanz holen.
Geht aber alles ohne static und ohne Singleton.
db8fs schrieb:> Referenziert 'Foo' irgendwie durch 'ne Member 'Bar' über diese> Singleton-Implementierung, kracht es. Bzw. es wird vollkommen> undurchschaubar, wann welches Singleton-Objekt zuerst entsteht.
Kannst du ein vollständiges Beispiel geben, bei dem es durch Probleme
mit der Initialisierungsreihenfolge kracht?
mh schrieb:> db8fs schrieb:>> Referenziert 'Foo' irgendwie durch 'ne Member 'Bar' über diese>> Singleton-Implementierung, kracht es. Bzw. es wird vollkommen>> undurchschaubar, wann welches Singleton-Objekt zuerst entsteht.>> Kannst du ein vollständiges Beispiel geben, bei dem es durch Probleme> mit der Initialisierungsreihenfolge kracht?
Vielleicht meint er das "static initialization fiasco"?
mh schrieb:> Kannst du ein vollständiges Beispiel geben, bei dem es durch Probleme> mit der Initialisierungsreihenfolge kracht?
Kann ich machen, dauert 'nen Moment.
Wilhelm M. schrieb:> Vielleicht meint er das "static initialization fiasco"?
Dieses fiasco existiert aber nicht, wenn man diese, von ihm als Scott
Mayers Singleton bezeichnete, Methode benutzt.
mh schrieb:> Wilhelm M. schrieb:>> Vielleicht meint er das "static initialization fiasco"?>> Dieses fiasco existiert aber nicht, wenn man diese, von ihm als Scott> Mayers Singleton bezeichnete, Methode benutzt.
Ok, da habe ich seinen Satz
1
Referenziert 'Foo' irgendwie durch 'ne Member 'Bar' über diese
2
Singleton-Implementierung, kracht es. Bzw. es wird vollkommen
3
undurchschaubar, wann welches Singleton-Objekt zuerst entsteht.
falsch verstanden bzw. jetzt erst richtig gelesen ... sorry for the
noise.
Das Konstruktionszeit-Problem entsteht genau dann, wenn man nicht
beliebige Initialisierungszeitpunkte für das Singleton zulässt bzw.
zulassen darf, weil ein Objekt sonst im falschen Thread-Local-Storage
oder so landen kann.
Also sprich, wenn man sicherstellen muss, dass es genau ein Objekt
gibt, dass zu genau einem definierten Zeitpunkt entsteht.
Neben Thread-Problematiken kann das auch für teuer zu erstellende
Objekte eine Rolle spielen, insbesondere mit dem Idiom "Resource
Allocation is Initialization" könnte das eine Rolle spielen.
D.h. das mal auf ein Beispiel übertragen: Singleton + Einschränkung des
Konstruktionszeitpunktes zur Laufzeit für das eine Objekt.
Folgendes müsste eigentlich zu einer Null-Pointer-Dereferenzierung
führen, wenn man den Konstruktionszeitpunkt einschränkt:
1
#include <iostream>
2
3
template <class T>
4
class Singleton
5
{
6
7
public:
8
// instance operator, converted to dynamic memory allocation for definining initialization time
9
static T & instance( bool init=false )
10
{
11
static T* obj( NULL );
12
13
if( init )
14
{
15
obj = new T();
16
}
17
18
return *obj;
19
}
20
21
22
// memleak ignored for this example
23
};
24
25
26
class Bar
27
{
28
friend class Singleton<Bar>;
29
30
Bar()
31
{
32
}
33
34
public:
35
void print() { std::cout << "bar" << std::endl; }
36
37
};
38
39
40
class Foo
41
{
42
friend class Singleton<Foo>;
43
44
Foo()
45
{
46
// null pointer dereference when constructed in wrong order
Ok, eigentlich doof, da Bar::print() zustandslos ist, bei Bedarf (wenns
halt noch nicht kracht), halt 'ne Abhängigkeit zum Objektzustand in das
print() reintun.
Also z.B. ne std::string-Member in Bar hinzufügen, deren Text im print()
ausgegeben wird.
mh schrieb:> Wilhelm M. schrieb:>> Vielleicht meint er das "static initialization fiasco"?>> Dieses fiasco existiert aber nicht, wenn man diese, von ihm als Scott> Mayers Singleton bezeichnete, Methode benutzt.
Im Prinzip haste recht damit - zumindest in 'ner idealen Welt frei von
jeglicher Nebenläufigkeit bzw. frei von Allokationsängsten.
Solche technischen Sorgen können allerdings trotz Verwendung des Idioms
Probleme entstehen lassen. Z.B. wenn eine Callback aus einem anderen
Threadkontext von einer Nachbarkomponente das Objekt zuerst
initialisieren würde - z.B. durch blödes Timing.
Hab zwar keine große Ahnung von .NET/Java, könnte mir aber vorstellen,
dass auch da der GarbageCollector nicht immer perfekt mitspielen könnte
- trotz des Meyers-Idioms.
db8fs schrieb:> Folgendes müsste eigentlich zu einer Null-Pointer-Dereferenzierung> führen, wenn man den Konstruktionszeitpunkt einschränkt:
Wobei man das so natürlich niemals programmieren würde.
Für mich ist das für ein "normales" Singleton eine Grundvoraussetztung,
dass der Initialisierungszeitpunkt und ggf. -thread nicht wichtig ist,
solange es vor der ersten Verwendung initialisiert ist. Wenn es da
Einschränkungen gibt, muss man sich darum zusätzlich kümmern. Aber das
muss man auch ohne Singleton.
Rolf M. schrieb:> Wobei man das so natürlich niemals programmieren würde.> Für mich ist das für ein "normales" Singleton eine Grundvoraussetztung,> dass der Initialisierungszeitpunkt und ggf. -thread nicht wichtig ist,> solange es vor der ersten Verwendung initialisiert ist. Wenn es da> Einschränkungen gibt, muss man sich darum zusätzlich kümmern. Aber das> muss man auch ohne Singleton.
:D
Wahre Worte - aber du kennst es sicher aus eigener Erfahrung, der
"Goldene Hammer" funktioniert oft sehr gut, z.B. in der Gestalt der
weisen Lehren aus'm Buch, die auf die eigenen Anwendungen angewendet
werden und wo man dann feststellt, dass halt irgendwas doch nicht ganz
passt.
Der Code oben ist auch schon paar Jahre alt, an den Architektursünden,
die durch die Singletons entstanden sind, hatte ich lange geknabbert,
bis die raus waren.
Bin heute sogar der Meinung, dass Singleton-Verwendungen häufige
Indizien für unvorteilhafte Architekturen sind.
db8fs schrieb:> Bin heute sogar der Meinung, dass Singleton-Verwendungen häufige> Indizien für unvorteilhafte Architekturen sind.
Meine Erfahrung ist, dass zu viel Verwendung von Singletons dazu führt,
dass der Code nicht mehr richtig modular ist. Überall kreuz und quer im
Code hat man Abhängigkeiten. Letztendlich genau die Probleme, wegen
derer globale Variablen so verpönt sind.
db8fs schrieb:> Folgendes müsste eigentlich zu einer Null-Pointer-Dereferenzierung> führen, wenn man den Konstruktionszeitpunkt einschränkt:
Das, was du in diesem Beispiel gemacht hast, ist aber ja *gerade kein
Singleton*. Es ist einfach nur eine globale Variable, die in einer
statischen Methode verpackt wurde.
Duch die if-Abfrage mit dem "init"-Parameter hast du ja quasi alles
ausgehebelt, was ein Singleton normalerweise tut: Nämlich die Ressource
genau einmalig bei Anforderung anlegen. Dein Dingen da legt die
Ressource aber nicht implizit bei Anforderung an, sondern nur explizit
(wenn "init" == true ist).
Natürlich kracht das. Das ist halt kein Singleton.
Hast du ja auch völlig richtig erklärt.
Nase schrieb:> Das, was du in diesem Beispiel gemacht hast, ist aber ja *gerade kein> Singleton*. Es ist einfach nur eine globale Variable, die in einer> statischen Methode verpackt wurde.
Ok, einverstanden, es ist eine globale Variable, die über den
template-Wrapper global zugänglich wird.
Gegenfrage: was ist denn das einzig wahre Singleton für C++?
> Duch die if-Abfrage mit dem "init"-Parameter hast du ja quasi alles> ausgehebelt, was ein Singleton normalerweise tut: Nämlich die Ressource> genau einmalig bei Anforderung anlegen.
Ok, wie stellst du sonst in C++ sicher, dass höchstens ein Exemplar der
Klasse zur Laufzeit existiert? Find das interessant und lass mich gerne
eines besseren Ansatzes belehren.
> Dein Dingen da legt die> Ressource aber nicht implizit bei Anforderung an, sondern nur explizit> (wenn "init" == true ist).
Jo, haste recht. Mit dem Hintergrundverweis auf die
C++-Laufzeitumgebung, welche für obige Verwendung (Multithreading, Calls
von anderen Plugins) halt Restriktionen auf die Implementierung
auferlegt. Zumindest was die Initialisierung betrifft. Ist halt so.
> Natürlich kracht das. Das ist halt kein Singleton.
Schön, wenn das natürlich so ist - wie wäre denn die richtige Lösung?
db8fs schrieb:> Mit dem Hintergrundverweis auf die C++-Laufzeitumgebung, welche> für obige Verwendung (Multithreading, Calls> von anderen Plugins) halt Restriktionen auf die Implementierung> auferlegt. Zumindest was die Initialisierung betrifft.
Die Initialisierung ist zumindest Thread-safe, so dass ein Static
höchstens 1× initialisiert wird und auch nicht konkurrierend von
mehreren Threads. Schau mal den generierten Code an für ein
nicht-triviales Beispiel, also für eines, wo das const nicht in .rodata
liegt und erst zu Laufzeit initialisiert wird.
Dynamic initialization of a block-scope variable with static storage duration or thread storage duration is performed the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
Ist also ganz klar geregelt.
Die "alte" Variante verlangt etwas mehr "Aufmerksamkeit":
http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
Das Paper ist m.E. der Grund, warum dieser o.g. Passus explizit in C++11
aufgenommen wurde.
Wilhelm M. schrieb:> Das Paper ist m.E. der Grund, warum dieser o.g. Passus explizit in C++11> aufgenommen wurde.
Und es ist eines der besten Papers zum Thema.
db8fs schrieb:> Gegenfrage: was ist denn das einzig wahre Singleton für C++?
Nun werde doch nicht dogmatisch.
Der Kerngedanke des Singleton-Patterns ist (war zumindest früher), eine
Ressource schludrig (lazu) bereitzustellen, nämlich bei Anforderung.
Diese Festlegung deckt sich mit allen mir bekannten Lektüren über
Entwurfsmuster.
Also genau das, was du da nicht machst, denn du initialisierst die
Instanz ja gerade explizit und zum Programmstart.
db8fs schrieb:> Ok, wie stellst du sonst in C++ sicher, dass höchstens ein Exemplar der> Klasse zur Laufzeit existiert? Find das interessant und lass mich gerne> eines besseren Ansatzes belehren.
1
structTheObject{};
2
3
structSingleton{
4
staticTheObject*getTheObject(){
5
staticTheObjectinstance;
6
return&instance;
7
}
8
};
Spätestens seit C++11 ist das sogar thread-wasserdicht.