C
tntnet schrieb:
Oder man macht kein meyers-singleton, sondern so:
Das minimiert die Locks und man hat selbst Kontrolle über die Instantiierung. Ist bei Alexandrescu beschrieben.
Die ganze Sache mit dem privaten Konstruktor, Destruktor und Zweisungsoperator hat den Sinn, daß der Compiler schon meckert, wenn man versucht, eine Instanz selbst anzulegen. Das kann schon mal versehentlich passieren, wie z. B.:
CKlasse instanz = CKlasse::getInstance(); // geht nicht, da Zuweisung privat ist
so geht es aber:
CKlasse& instanz = CKlasse::getInstance();
Ein Nachteil hat das ganze: Die Klasse wird nie zerstört. Aber das will man ja eigentlich auch gar nicht. Das fällt aber auf, wenn man einen Memory-leak-debugger nutzt. Der bemängelt, daß die Instanz nach Programmende nicht freigegeben wurde.
Tntnet
leider ist auch das nicht thread-safe. der zeiger instance sollte auf jeden fall extern linkage haben. nur dann kann man argumentieren, dass das erstellen eines Lock objekts potentiell auswirkungen auf seinen zustand hat (und so den compiler daran hindern, den code so zu transformieren, dass etwa das zweite if vor dem lock steht). das andere - wesentlichere - problem ist die sichtbarkeit von veränderungen an instance. ohne ein geeignetes speichermodell, das C++ gegnwärtig nicht hat, ist dieses problem prinzipiell nicht allgemein lösbar. die tatsache, das so etwas auf single-prozessor-systemen oder typischen multicore-pc-systmen funktioniert, bedeutet nicht, das diese konstruktion an sich sicher ist.
Edit: es müßte genügen, instance volatile zu machen, anstatt ihm extern linkage zu geben. zwar ist der einzige weg, auf instance zuzugreifen, der aufruf von getInstance(), aber genau das könnte der Lock-Konstruktor ja tun. und da zugriffe auf volatile variablen und die reihenfolge dieser zugriffe beobachtbar sind; genügt das, um die beschriebene umordnung zu verhindern (vorausgesetzt, der compiler bekommt bestimmte teile des codes von Lock nie zu sehen). Ein anderes problem ist Mutex. Da es sich vermutlich nicht um ein POD handelt, wird der compiler die initialisierung erst beim ersten aufruf initialisieren. aber wann ist denn der erste aufruf, wenn die funktion aus mehreren threads aufgerufen wird ?