Singleton-Muster
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 91 von 104.
Das Singleton-Muster stellt sicher, dass eine Klasse während des gesamten Programms nur eine Instanz besitzt, und bietet einen globalen Zugriffspunkt darauf. Dies ist nützlich für Ressourcen, die gemeinsam genutzt werden sollten, wie etwa einen Konfigurationsmanager, Logger oder Datenbankverbindungspool.
Die zentrale Idee besteht darin, den Konstruktor privat zu machen, sodass niemand direkt Instanzen erstellen kann, und anschließend eine statische Methode bereitzustellen, die die einzelne Instanz zurückgibt:
#include <iostream>
#include <string>
class Logger {
private:
std::string logFile;
// Privater Konstruktor – verhindert direkte Instanziierung
Logger() : logFile("app.log") {}
// Copy-Konstruktor und Zuweisungsoperator löschen
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
public:
// Statische Methode zum Zugriff auf die einzige Instanz
static Logger& getInstance() {
static Logger instance; // Wird einmal erstellt, lebt bis zum Programmende
return instance;
}
void log(const std::string& message) {
std::cout << "[LOG] " << message << "\n";
}
};
int main() {
Logger::getInstance().log("Application started");
Logger& logger = Logger::getInstance();
logger.log("Processing data");
}Das static Logger instance innerhalb von getInstance() ist ein C++11-Feature namens „Magic Statics“ – es ist threadsicher und wird garantiert nur einmal initialisiert. Indem wir den Kopierkonstruktor und den Zuweisungsoperator löschen, verhindern wir, dass jemand Kopien unseres Singletons erstellt.
Verwende Singleton sparsam – es führt einen globalen Zustand ein, der das Testen erschweren kann. Es eignet sich am besten für wirklich gemeinsam genutzte Ressourcen, bei denen mehrere Instanzen Probleme verursachen würden.
Aufgabe
EinfachErstellen wir ein GameSettings-Singleton, das die Konfiguration für eine Spieleanwendung verwaltet. Dies ist ein perfekter Anwendungsfall für das Singleton-Muster. Du möchtest genau einen Einstellungsmanager, auf den alle Teile deines Spiels zugreifen können, um eine konsistente Konfiguration im gesamten Spiel sicherzustellen.
Du organisierst deinen Code auf drei Dateien verteilt:
GameSettings.h: Definiere deine Singleton-Klasse, die die Spielkonfiguration verwaltet.Deine Klasse
GameSettingssollte drei Einstellungen als private Member speichern:difficulty(String),volume(Ganzzahl von 0–100) undfullscreen(boolescher Wert). Initialisiere sie mit den Standardwerten:"Normal",50undfalse.Implementiere das Singleton-Muster korrekt:
- Der Konstruktor muss private sein
- Lösche den Kopierkonstruktor und den Zuweisungsoperator
- Stelle eine statische Methode
getInstance()bereit, die eine Referenz auf die einzige Instanz zurückgibt
Füge diese öffentlichen Methoden hinzu, um mit den Einstellungen zu interagieren:
setDifficulty(const std::string& diff): aktualisiert den SchwierigkeitsgradsetVolume(int vol): aktualisiert die LautstärkesetFullscreen(bool fs): aktualisiert den VollbildmodusdisplaySettings(): gibt alle aktuellen Einstellungen aus
GameModule.h: Erstelle ein separates Modul, das auf das Singleton zugreift.Definiere eine Funktion namens
applyGamePreset, die einen Preset-Namen (String) entgegennimmt. Diese Funktion sollte auf dasGameSettings-Singleton zugreifen und es abhängig vom Preset konfigurieren:"casual": setzt den Schwierigkeitsgrad auf"Easy", die Lautstärke auf70und den Vollbildmodus auffalse"competitive": setzt den Schwierigkeitsgrad auf"Hard", die Lautstärke auf30und den Vollbildmodus auftrue- Jedes andere Preset: behält die Standardeinstellungen unverändert bei
Dies zeigt, wie verschiedene Teile eines Programms auf dieselbe Singleton-Instanz zugreifen und sie ändern können.
main.cpp: Führe alles zusammen, um das Singleton in Aktion zu zeigen.Lies zwei Eingaben ein:
- Einen Preset-Namen (String)
- Einen benutzerdefinierten Lautstärkepegel (Ganzzahl)
Gib zuerst die anfänglichen Standardeinstellungen aus, indem du
displaySettings()für das Singleton aufrufst. Gib danach eine Leerzeile aus.Wende anschließend das Preset mit deiner Funktion
applyGamePresetan und gib die Einstellungen erneut aus. Gib danach eine Leerzeile aus.Überschreibe schließlich nur die Lautstärke mit dem benutzerdefinierten Eingabewert und gib die endgültigen Einstellungen aus.
Die Methode displaySettings() sollte exakt in diesem Format ausgeben:
Difficulty: [value]
Volume: [value]
Fullscreen: [yes/no]Zum Beispiel mit den Eingaben casual und 85:
Difficulty: Normal
Volume: 50
Fullscreen: no
Difficulty: Easy
Volume: 70
Fullscreen: no
Difficulty: Easy
Volume: 85
Fullscreen: noMit den Eingaben competitive und 20:
Difficulty: Normal
Volume: 50
Fullscreen: no
Difficulty: Hard
Volume: 30
Fullscreen: yes
Difficulty: Hard
Volume: 20
Fullscreen: yesBeachte, wie sowohl main.cpp als auch GameModule.h auf dieselbe Singleton-Instanz zugreifen: Änderungen, die an einer Stelle vorgenommen werden, sind überall sichtbar. Das ist die Stärke des Singleton-Musters für gemeinsam genutzte Ressourcen wie Spieleinstellungen.
Probier es selbst
#include <iostream>
#include <string>
#include "GameSettings.h"
#include "GameModule.h"
using namespace std;
int main() {
// Eingaben lesen
string presetName;
int customVolume;
cin >> presetName;
cin >> customVolume;
// TODO: Hole die GameSettings-Singleton-Instanz
// TODO: Zeige die anfänglichen Standardeinstellungen an
// TODO: Gib eine leere Zeile aus
// TODO: Wende das Preset mit der Funktion applyGamePreset an
// TODO: Zeige die Einstellungen nach dem Anwenden des Presets an
// TODO: Gib eine leere Zeile aus
// TODO: Überschreibe die Lautstärke mit customVolume
// TODO: Zeige die endgültigen Einstellungen an
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie10STL-Übersicht
STL-Übersicht & PhilosophieSTL-ContainerIteratorenSTL-AlgorithmenFunktoren & Lambda-AusdrückeRückblick – Worthäufigkeit13Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory und Abstract FactoryBuilder-MusterBeobachter-MusterStrategie-Muster2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler