ubuntuusers.de

OOP/C++:Erzeugungsmuster Singleton

Status: Gelöst | Ubuntu-Version: Xubuntu 14.04 (Trusty Tahr)
Antworten |

Dakuan

Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6493

Wohnort: Hamburg

Ich unternehme gerade einen erneuten Versuch, mich in die Entwurfsmuster einzuarbeiten, was mir wegen des hohen Grades an Abstraktion nicht ganz leicht fällt.

Vorgeschichte: In meiner Anwendung verwende ich bisher zur Speicherung von benutzerdefinierten Einstellungen die Klasse Fl_Preferences, die ich direkt als statische Methode des Hauptfensters verwende. Aber die Kommunikation mit den verschiedenen Programmmodulen konnte ich noch nicht zufriedenstellend lösen.

Jetzt erscheint mir das Singleton Muster als eine geeignete Lösung, da es quasi eine globale Klasse ist, die ich anderen Klassen nicht explizit übergeben muss.

Aber ich habe da noch ein Problem, denn (fast) alle Bücher zu diesem Thema verwenden als Beispielsprache Java, was dazu führt, das ich einigen Details nicht folgen kann. Da wird z.B. erwähnt das diese Methode "threadsicher" ausgeführt sein muss. Im Java Beispiel sieht das dann so aus:

1
public static synchronized Konfig gibInstance();

Aber was muss ich da in C++ machen oder beachten um das zu gewährleisten?

Wobei ich noch anmerken muss, dass hier ich eigentlich keine Threads verwende. Aber ich weiß nicht was mein Toolkit mit seinem Menüsystem da veranstaltet.

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4683

Wohnort: Berlin

@Dakuan: Wenn Du keine Threads verwendest musst Du es auch nicht thread-sicher machen. Und wenn Du es thread-sicher machen müsstest, dann reicht es ja nicht diese eine Funktion/Methode thread-sicher zu machen, dann müsste die gesamte Klasse abgesichert werden. Falls sie das nicht ist, müsstest Du eine thread-sichere Fassade/Wrapper-Klasse um die Klasse schreiben.

Mit FLTK kenne ich mich nicht aus, aber GUI-Toolkits sind in der Regel nicht thread-sicher, beziehungsweise oft nur ein sehr kleiner Teil davon, der dann auch die Schnittstelle zu Threads ist. Wenn man sie denn verwendet.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6493

Wohnort: Hamburg

Ich merke, Du kennst dich aus. Bis zur Fassade bin ich noch nicht richtig vorgedrungen, ich hänge noch beim Dekorator (wobei ich festgestellt habe, das ich diesen bereits verwendete ohne den Namen zu kennen).

Und wenn Du es thread-sicher machen müsstest, dann reicht es ja nicht diese eine Funktion/Methode thread-sicher zu machen, dann müsste die gesamte Klasse abgesichert werden.

In dem Buch Entwurfsmuster geht es, soweit ich das verstanden habe, erstmal nur darum das in einem System mit mehreren Threads wirklich nur ein solches Objekt erzeugt werden kann. Wahrscheinlich kann man das sicher stellen indem man das Objekt erzeugt bevor die Threads gestartet werden.

Aber muss ich danach noch weiteres beachten, außer natürlich in den Methoden statische Variablen zu vermeiden.

Das Schlüsselwort "synchronized" macht mich stutzig.

Mit FLTK kenne ich mich nicht aus, aber GUI-Toolkits sind in der Regel nicht thread-sicher, beziehungsweise oft nur ein sehr kleiner Teil davon, der dann auch die Schnittstelle zu Threads ist. Wenn man sie denn verwendet.

Ich habe das auch nur der Vollständigkeit halber erwähnt, um meinen Post nachvollziehbar zu machen. Jedenfalls werden aktuelle FLTK Anwendungen bei Verwendung der Standard Einstellungen mit den Optionen:

-D_THREAD_SAFE -D_REENTRANT

übersetzt, was immer das bedeuten mag.

Also, die aktuelle Anwendung verwendet keine Treads. Aber ich habe eine andere Anwendung, die Treads benutzt und 2 weitere, wo ich überlege, ob das Vorteile bringt. Daher interessiert mich schon, was ich da gff. beachten muss, oder ob ich das unter bestimmten Bedingungen vernachlässigen kann.

Bisher leiden leider alle meine Anwendungen darunter, das individuelle Einstellungen des Benutzers nicht in gewünschter Weise gespeichert werden. So etwas sollte ja eigentlich nicht "tread kritisch" sein, oder etwa doch?

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4683

Wohnort: Berlin

@Dakuan:: Bei der gezeigten Funktion/Methode geht es in der Tat darum, dass auch bei mehreren Threads sichergestellt ist, das wirklich nur genau ein Objekt erstellt werden kann. Ob das nötig ist, auch wenn Threads verwendet werden, und ob und was sonst noch in diesem Fall nötig ist, kann man so ganz allgemein nicht sagen. Es sei denn man will die gesamte Klasse gegen fast jedes mögliche Szenario absichern, was in Java recht einfach möglich ist mit synchronized bei jeder Methode, was bedeutet das dann grundsätzlich nur eine Methode zu jedem gegebenen Zeitpunkt aktiv sein kann und die anderen jeweils warten müssen.

Du hast in Java bei jedem Objekt und jeder Klasse ein Lock dabei und synchronized bei einer Methode sorgt dafür das dieses Lock am Anfang der Methode angefordert und am Ende der Methode wieder freigegeben wird. Bei normalen Methoden ist es das Lock vom Objekt und bei statischen das Lock für die Klasse.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Foo {
    synchronized public static void foo() {
        // do something
    }
}

// ist äquivalent zu

class Foo {
    public static void foo() {
        synchronized(Foo.class) {
            // do something
        }
    }
}

// was wiederrum in etwa Folgendem entspräche, wenn man explizit selbst
// für Lock-Objekte sorgen müsste

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Foo {
    public static Lock lock = new ReentrantLock();

    public static void foo() {
        try {
            lock.lock();
            // do something
        } finally {
            lock.release();
        }
    }
}

Ob Du statische oder Membervariablen verwendest ist bei einem Singleton egal, denn das es dieses Objekt nur einmal gibt, macht das keinen wirklichen Unterschied. Wenn mehrere Threads darauf operieren, greifen sie auf die selben Daten zu, was problematisch sein kann.

Für multithreaded Programme scheint bei FLTK ähnliches zu gelten wie bei den meisten GUI-Rahmenwerken: Man darf nicht alles in den Threads machen in denen nicht die GUI-Hauptschleife läuft, und es gibt ein paar spezielle Funktionen/Methoden um mit diesem Thread zu kommunizieren und um sich ein Lock zu holen und das wieder freizugeben. Das gesamte Kapitel Advanced FLTK in der FLTK-Dokumentation dreht sich um Threads.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6493

Wohnort: Hamburg

Ich glaube, ich habe es jetzt.

Du hast in Java bei jedem Objekt und jeder Klasse ein Lock dabei und synchronized bei einer Methode sorgt dafür das dieses Lock am Anfang der Methode angefordert und am Ende der Methode wieder freigegeben wird.

Dann würde ich in C/C++ das Problem mit einem Mutex lösen können?

In meinem Buch wurde als Beispiel die Laufnummern Vergabe für eine Dokumentenverwaltung erwähnt, wobei die Anfragen auch übers Netzwerk kommen könnten.

Ob Du statische oder Membervariablen verwendest ist bei einem Singleton egal, denn das es dieses Objekt nur einmal gibt, macht das keinen wirklichen Unterschied.

Ist logisch. Wieder einmal nicht zuende gedacht.

Das gesamte Kapitel Advanced FLTK in der FLTK-Dokumentation dreht sich um Threads.

Du glaubst gar nicht, wie oft ich mir das Kapitel schon reingezogen habe.

Ich gehe jetzt mal davon aus, das ich keine besonderen Vorkehrungen treffen muss, solange ich keine eigenen Threads erstelle und auch die Netzwerk Schnittstelle nicht anfasse.

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4683

Wohnort: Berlin

@Dakuan:: In C++11 heisst das Ding tatsächlich mutex. Vor C++11 muss man eine externe Threadbibliothek verwenden, wie beispielsweise pthreads oder etwas von Ratioph… Boost. ☺

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6493

Wohnort: Hamburg

Mit pthreads hatte ich schon mal das Vergnügen.

Der Buchautor hätte das Schlüsselwort "synchonized" ruhig eingehender erklären können, damit auch ich das verstehe 😉 Schließlich klebt auf dem Buch ein Aufkleber: Für alle OOP-Sprachen geeignet.

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4683

Wohnort: Berlin

@Dakuan:: Wenn es für alle OOP-Sprachen geeignet sein soll dann hätte er nicht synchronized in Java eingehender erklären müssen, sondern was dort abgesichert werden muss. Ich habe mal in die Beispielquelltexte geschaut, die man von der Buchwebseite herunterladen kann. Da steht folgender Code in einer Klasse Konfiguration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        public static synchronized Konfiguration gibInstanz()
        {
                if (instanz == null)
                {
                        synchronized (Konfiguration.class)
                        {
                                if (instanz == null)
                                        instanz = new Konfiguration();
                        }
                }
                return instanz;
        }

Das sieht IMHO so aus als hätte der Autor die Methodendeklaration static synchronized selbst nicht so ganz verstanden…

Auskommentiert folgt dann die sinnvolle Variante mit einem Test und ohne sinnfreies zweites sperren.

In der Klasse gibt es dann noch eine getWert()- und eine setWert()-Methode. Beide nicht thread-sicher und die setWert() ruft auch noch eine schreibeKonfiguration() auf die laut Kommentar die Konfiguration auf den Datenträger schreibt. Was soll da schon schief gehen… 😉

Letztlich kann ein Buch für Entwurfsmuster nicht so wirklich für alle OOP-Sprachen sein, denn die Sprachen haben unterschiedliche Idiome und nicht jedes Muster ist auch in jeder Sprache ein Muster. Zudem lösen einige der klassischen Entwurfsmuster Probleme mit statischen Sprachen die man in dynamischen Sprachen so nicht wirklich hat, dort machen sie also nicht unbedingt Sinn.

Ich habe jetzt spasseshalber mal das Iterator-Beispiel in Java angeschaut und ich hoffe in dem Buch steht auch das Java bereits in der Standardbibliothek ein entsprechendes Interface hat. So würde man das in Java also ganz bestimmt nicht lösen. Von Mustern gibt es oft auch Variationen. Statt istFertig(), weiter(), und aktuellesElement() kann man die Schnittstelle beispielsweise auch mit einer naechstesElement()-Methode umsetzen die entweder das nächste Element liefert oder eine Ausnahme auslöst wenn es kein weiteres Element gibt. Auch das wäre ein Iterator. Und es muss nicht zwingend eine iteriere()-Methode für ”interne Iteration” geben. Andererseits kann man aber auch *nur* diese Methode anbieten. Oder darauf aufbauend gleich einen ganzen Zoo von internen Iterations-, Aggregations-, und Testmethoden.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6493

Wohnort: Hamburg

Ich habe mir das Singleton-Muster jetzt mal im Buch "Entwurfsmuster von Kopf bis Fuß" angesehen. Bei meinem ersten Anlauf hatte ich das nach kurzer Zeit wieder weg gelegt, das war mir damals irgendwie zu abstrakt.

Übrigens, der Programmcode ist in beiden Büchern fast identisch!

In der Klasse gibt es dann noch eine getWert()- und eine setWert()-Methode. Beide nicht thread-sicher ...

Ich denke es gibt hier 2 Probleme.

  • Einmal die threadsichere Erstellung genau eines Objekts.

  • Die übliche Synchronisation bei Verwendung mehrerer Threads, die hier weggelassen wurde.

Letztlich kann ein Buch für Entwurfsmuster nicht so wirklich für alle OOP-Sprachen sein, denn die Sprachen haben unterschiedliche Idiome und nicht jedes Muster ist auch in jeder Sprache ein Muster.

Ja, leider. Die Autoren scheinen sich auch alle irgendwie auf Java geeinigt zu haben. C++ wird nur am Rande mal erwähnt.

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4683

Wohnort: Berlin

@Dakuan:: Zu Entwurfsmustern gehören eigentlich auch immer konkrete Beispiele. Also keine die sich der Autor des jeweiligen Buches ausgedacht hat, sondern konkreter Code in echten Projekten. Die Muster werden ja nicht erfunden, sondern entdeckt — man findet eine gute Lösung für ein wiederkehrendes Problem in mehreren unabhängigen Projekten in der gleichen Art und Weise und gibt diesem entdeckten Muster dann einen Namen. Wenn ein Buch also zu abstrakt sein sollte weil Verweise auf solche realen Beispiele fehlen, die man sich anschauen kann, fehlt da etwas.

An der Stelle spielt dann wieder die Sprache mit hinein: gibt es Verweise auf Anwendung des Musters in der Sprache in der man letztlich programmiert. Der Klassiker „Design Patterns“ von Gamma, Helm, Johnson, Vlissides (a.k.a. „Gang of Four“/GoF) hat seine Beispiele in SmallTalk und C++.

Ich denke das Hauptproblem bei dem synchronized ist, dass es mit dem eigentlichen Singleton nichts zu tun hat. Ob das thread-sicher ist oder nicht ist ein völlig orthogonales Problem.

Es gibt sicher auch Bücher die C++ als Sprache für die realen und die ”Spielzeug”-Beispiele haben. C++ hat ja doch eine gewisse Verbreitung. Allerdings sind C# und Java für Buchautoren wahrscheinlich lukrativer weil es dafür vermutlich mehr Anfänger/Schüler/Studenten gibt.

Letztlich muss man bei den Mustern schauen was sie erreichen sollen und wie sie das (abstrakt gesehen) erreichen, und das dann versuchen mit den Sprachmitteln umzusetzen die einem die konkrete Sprache bietet, in der man es dann umsetzen möchte. Beim Singleton ist das Ziel das der Programmierer nur ein einziges Exemplar erstellen kann. In Java verhindert man dafür das man den Konstruktor von aussen aufrufen kann und bietet eine statische Methode die das kann und die sicherstellt, das es tatsächlich nur einmal passiert. Das kann man in C++ auch so machen. Konstruktor protected und eine public static-Methode die entweder das einzige bereits bestehende Exemplar zurück gibt, oder es erstellt und zurück gibt.

Da hast Du mit Java und C++ noch Glück weil das Singleton dort relativ einfach von Java auf C++ übertragbar ist. Bei anderen Sprachen muss man etwas mehr überlegen wie man das umsetzt. Bei Python zum Beipiel, kann man die Initialisierungsmethode oder den Konstruktor nicht ”private” machen. Aber dafür hat die Sprache tatsächlich einen Konstruktor, also eine Methode die nicht nur ein bereits vorhandenes Objekt initialisiert (__init__()), was der Java-Konstruktor eigentlich nur macht, sondern auch eine Methode die das Objekt konstruiert/erstellt (__new__()). An der Stelle kann man dann entscheiden ob man ein neues Objekt erstellt, oder ein bereits bestehendes liefert, und so das Singleton-Muster umsetzen. Andererseits bietet Python aber auch schon Singletons für die man gar nichts weiter implementieren muss: Module. Die werden durch's erste importieren erstellt, während jeder weitere import immer wieder das selbe Modul-Objekt liefert, welches beim ersten import erstellt wurde.

Wo man auch aufpassen muss ist das man nicht anfängt Code/Implementierungen für ein Muster von Sprache X einfach 1:1 auf Sprache Y zu übertragen. Ich weiss zum Beispiel nicht ob die Umsetzung des Singletons von Java auf C++ in C++ wirklich idiomatischen Code zur Folge hat, denn in C++ könnte man vielleicht ähnlich wie in Python den new-Operator überschreiben. Und den Destruktur private oder protected deklarieren damit niemand Exemplare auf dem Stack erstellen kann.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6493

Wohnort: Hamburg

Wenn ein Buch also zu abstrakt sein sollte weil Verweise auf solche realen Beispiele fehlen, die man sich anschauen kann, fehlt da etwas.

Inzwischen sehe ich das etwas anders. Ich war wohl noch nicht reif dafür und hatte mich dann außerdem von den Beispielen mit den Enten ablenken lassen. Bei Badeenten muss ich immer an Loriot denken 😉

Ich denke das Hauptproblem bei dem synchronized ist, dass es mit dem eigentlichen Singleton nichts zu tun hat.

Genau, wenn in den Beispielen etwas auftaucht, was man nicht einordnen kann, hat man immer das Gefühl etwas wesentliches nicht mit zu kriegen.

Das kann man in C++ auch so machen. Konstruktor protected und eine public static-Methode die entweder das einzige bereits bestehende Exemplar zurück gibt, oder es erstellt und zurück gibt.

Ja, solche Konstrukte habe ich im FLTK Quelltext schon mehrfach gesehen, und mich darüber gewundert. Ob das jetzt aber alles Sigletones sind, kann ich nicht erkennen, weil da oft recht tief in die Trickkiste gegriffen wird (jedenfalls aus der Sicht eines C++ Anfängers).

Antworten |