Hvordan man laver en singleton klasse

Tags:    c++
Skrevet af Bruger #2730 @ 31.05.2003
En Singleton er et objekt der kun kan have en lovlig instans ad gangen, dette betyder at man kun kan have en enkelt singleton klasse ad gangen mens programmet kører. Hvis der forsøges at oprette en ny instans af klassen vil programmet generere en fejl, hvis det er i debug mode. Singleton klasser er meget nyttige i specielt computerspil til brug når man laver for eksempel fjender, lydadministratorer, objektadministratorer og så videre. De tilbyder adgang fra hele dit program via statiske members på klassen. Singletons er baseret på statiske pointere til klasser. Ved at holde en statisk pointer til instansen af klassen og ved at have en statisk klasse funktion der returnerer denne pointer, kan man altid vide om der er en aktiv instans af objektet, hvis det er tilfældet kan man så bruge denne instans, ellers melder programmet fejl. Nedenstående er en komplet implementation af en simpel singleton klasse.
Fold kodeboks ind/udKode 

Ovenstående er implementationen af CSingleton klassen, der er en meget simple singleton klasse. Vi vil nu gennemgå koden trin for trin således man også forstår hvad der sker og ikke bare hovedløst implementerer en Singleton klasse på baggrund af ovenstående. Den første #include statement kommer vel næppe bag på nogen, det er input-output stream vi inkluderer, således vi blandt andet kan skrive til vores konsol. Anden linie (#include<assert.h>
) er måske ny for nogle. Vi inkluderer denne header i vores program således vi kan bruge kommandoen assert(BOOL), den vil jeg gennemgå når vi når til den linie. I Tredje kodelinie angiver vi at det er det namespace der hedder std vi vil bruge gennem vores kode, dette gør blandt andet at vi har fuld adgang til de funktioner der findes i dette namespace uden at skulle skrive eksempelvis std foran vores funktioner (std::cout<<”test”<<endl;). Dette namespace hjælper egentligt ikke i dette lille program vi udvikler, men jeg kan godt lide atltid at have det med, således min kode er nemmere at tilpasse til andre namespaces senere hen. De næste 10 linier er vores klasse definition af Singleton klassen, bemærk at klassen hedder Csingleton i stedet for Singleton, dette er en almindelig navngivningsregel, at klasser bliver skrevet med et stort C foran således man kan se at det er en klasse man taler om. Det mest interessante i denne klasse definition er at der findes et statisk member /attribut i ’private’ scope der er en pointer til typen Csingleton, det vi lsige sig selv. Desuden indeholder klassedefinitionen en statisk metode til at returnere dette member/attribut. Da vores attribut m_singleton er erklæret statisk vil den have samme værdi alle de gange den bliver erklæret, det er rent faktisk dette vi bruger til at bygge vores singleton klasse ud fra. Alle andre metoder på denne klasse er constructor og destructor. Går vi videre fra klasse definitionen kommer vi til selve implementationen af vores klasse. Det første vi gør er at lave en ny instans af vores statiske variabel m_singleton (igen ser vi navngivningsreglen, denne gang sætter vi et m foran en variabel, for at vise at det er et member/attribut). Det der kan virke lidt tricky ved dette er at det rent faktisk er memberet fra klassen selv der er lavet som en pointer til objektet. At lave denne pointer til objektet Singleton kan gøres med følgende kode:
Fold kodeboks ind/udKode 

Når dette er gjort skal vi implementere vores constructor, dette er relativt simpelt. Vi vil dog tage højde for at det ikke er muligt at lave mere end een instans af vores singleton. Dette gør vi med brugen af headerfilen assert.h. Først et par ord om assert.h headerfilen. Header filen assert.h indeholder en funktion der hedder assert(BOOL) den tager en bool variabel (altså true eller false) som parameter. Hvis værdien der står i funktionen er false blever der lavet i breakpoint i debuggeren på denne linie, dette gør det nemmere bagefter at dianosticere dette problem. Reelt vil det virke på den måde at så snart man forsøger at lave en anden instans af denne singleton klasse i sit program vil man få en fejl (i debug mode), programmet vil gå ned og man kan fejlfinde. Det er vigtigt at programmet går ned og giver besked om hvor der er sket en fejl (assert laver jo et breakpoint vi senere kan finde igen) da vi i vores program netop ikke ønsker at lve en anden instans af vores Singleton klasse. Kigger vi på koden til constructoren ser den ud som følger:
Fold kodeboks ind/udKode 

Det første der sker i constructoren er at vi lige kontrollerer at der ikke eksisterer en instans af denne klasse i forvejen, dette gøres ved at spørge med !-tegn foran vores pointer til en klasse, hvis der eksisterer en i forvejen bliver dette tjek false og vores program brager ned (hvilket er godt, da vi så ikke må lave en ny instans). Hvis ikke der i forvejen findes en instans af denne klasse bliver resultatet af vores assert til assert(!false) og not-false er som bekendt true, dette gør at vi får lov til at lave en ny instans af denne klasse. Efter dette hurtige tjek, der er alpha og omega i vores singleton klasse, er det tid til at oprette en ny instans af denne klasse, dette gøres ved at føje pointeren til klassen til den klasse vi er ved at definere (lyder lidt kryptisk, men det er rent faktisk det der sker):
Fold kodeboks ind/udKode 

Denne kode laver den ny instans af klassen Csingleton. Denne instans er den eneste vi ønsker i vores program, så alt virker som det skal. De to sidste funktioner er relativt hurtige at løbe igennem når vi forstår hvordan funktionen assert virker. Den første er metoden til at hente instansen af vores Csingleton klasse:
Fold kodeboks ind/udKode 

Det første vi gør er lige at teste om der findes en instans af klassen inden vi returnerer den til metoden, hvis ikke dette er tilfældet (det vil sige vi ikke har kaldt constructoren og fået oprettet en ny instans) fejler vores program. Hvis vi har husket at kalde constructoren og dermed have en lovlig instans af vores Csingleton klasse kan vi herefter returnere den til metoden. Rimeligt simpelt. Den næste metode er destructoren, den er lidt mere tricky, da vi kun har statiske værdier, er vi nødt til selv at fortælle at instansen af klassen nu er nedlagt. Dette kan gøres ved at sætte vores pointer til at pege på NULL.
Fold kodeboks ind/udKode 

Igen tester vi lige at der findes en instans af klassen ved at bruge vores assert metode, det giver ingen mening at nedlægge et objekt der ikke eksisterer, derfor dette tjek. Går det godt (det vil sige at m_singleton memberet er true, og at der eksisterer en instans, så vi kan nedlægge den) sætter vi pointeren til NULL og objektet er nedlagt.

Det næste vi vil gøre er at teste vores Singleton klasse med en main metode (denne kan bare skrives i bunden af alt det andet kode og kompileres med, jeg har skilt det ud for overskuelighedens skyld).
Fold kodeboks ind/udKode 

Det første vi laver i main metoden er at lave en instans af vores objekt, dette gøres ved at kalde constructoren. Det næste vi vil gøre er at lave en pointer til vores CSingleton objekt (for god ordens skyld er et objekt en klasse der er lavet en instans af) således vi kan tilgå de andre metoder vi senere hen ville kunne lave derpå, som fx playSound(), loadObjects() og så videre. Når denne pointer er på plads sætter vi pointeren til at pege på vores nyligt kreerede Csingleton klasse, til dette formål bruger vi vores metode getSingleton(), der jo som bekendt returnerer en pointer til det objekt der lige er oprettet (derfor er den lidt speciel nede i vores implementation af metoden). Hvis vi nu compiler det og kører det ser vi ingen fejl, men hvis vi derimod laver en ny instans:
Fold kodeboks ind/udKode 

Når vi kører denne mode i debug mode vil den få programmet til at gå ned og der vil komme en fejlmeddelelse, der fortæller i hvilken linie det gik galt og at det ikke var muligt at lave en assertion (assertion betyder påstand på engelsk, så det giver rent faktisk mening at det ikke var muligt at lave en påstand når programmer fejler).

Afrunding
Singleton klasser benyttes meget i spiludvikling, hvor det er ønskeligt kun at have een instans af en given fjende, eller kun een instans af den klasse der loader og afspiller lydfiler. Måden vi har gjort det på er ved at lave vores program i debug mode hvor assert vil fejle, i dette tilfælge går man ud fra at hele programmet kodes og testes i alle leder og kanter i debug mode. Dette gør at man kan se hvor i ens kode man forsøger at oprette nye instanser af de Singletonklasser man har erklæret, herefter kan man så rette i sin kode til alt dette er fjernet. Så kan man compile den til release og frigive sin splinter nye applikation med Singleton klasser. Dette program er udviklet og testet i Visual Studio 6.0 jeg ved ikke hvordan det vil virke i andre miljøer.



Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (13)

User
Bruger #2438 @ 02.06.03 15:45
Hvorfor laver du en virtual destructor?
User
Bruger #3116 @ 08.06.03 10:21
Hvad med at lave constructoren private og saa lade getSingleton oprette klassen, hvis objektet ikke eksisterer?
User
Bruger #1445 @ 11.06.03 14:24
Jeg har svær at se hvad det skal bruges til. er det blot fordi der ikke må oprette mere end et objekt af klassen ?....
Hvis dette er tilfældet, hvor så ikke blot lave en statisk var og forøge denne hver gang der oprettes et objekt.

Hvis det der imod har noget med Pure Virtuel at gøre forstår jeg det slet ikke... Måske fordi jeg ikke kan forstå hvad anvendelse kunne være. Sorry....

(Hvis det er Pure Virtuel, så se lige MSDN for en nemmere måde at lave dette på)...

"Pure Virtuel" tvinger en klasse der arver til at indeholde methoden....
User
Bruger #2730 @ 12.06.03 09:07
Singletonklassen kan kun eksistere i eet objekt, der kan aldrig eksistere flere objekter af denne klasse på noget tidspunkt. Det er meget praktisk at have en public constructor til en singleton klasse, der kan i princippet være overloadede constructorer. Enhver klasse kan jo i princippet være en singleton klasse, det eneste der lidt specielt ved denne er at når man debugger vil man få en fejl i sin kørsel hvis man opretter et nyt objekt af en singleton klasse, får man denne fejl ændrer man sin kode og tester videre indtil alle fejl er elimineret. Når alt er elimineret compiler man til release.
User
Bruger #1445 @ 16.06.03 12:47
Ok.... Mange tak for forklaringen.
User
Bruger #884 @ 27.06.03 12:51
At skrive der genereres en fejl hvis der forsøges at oprette felre objekter er ikke helt korrekt formuleret.
Med en singleton forståes at der kun kan oprettes en instans af klassen. Hvis nu der er flere klasser A B og C der alle ønsker at benytte singleton klassen S, kan de ikke se hvem det er der faktisk laver objektet. man kan oprette fra alle klasserne A B C , men hvis objektet er oprettet, får man blot en reference til dette. Denne funktionalitet kan laves i S ved at denne indeholder en instans af sig selv. Med en privat constructor og en public statisk metode på klassen kan andre så benytte denne singleton. S ser som om den er "oprettet" og hvis den er returner en reference ;o) smart og enkelt og virker. kan evt. benyttes i et broker pattern hvor mange referer til samme broker. eller i en windows applikation hvor der kun eksister en instans af vinduer
User
Bruger #4176 @ 05.07.03 21:00
Interresant ville det være, hvis man kunne lave en generisk singleton klasse. en Config klasse kunne eksempelvis arve fra singleton og dermed selv blive en singleton. findes der en løsning på det?

mht din statiske singleton-creator så er den helt fin og den har jeg også selv brugt. så slipper man også for asserts som i eksemplet.
User
Bruger #4699 @ 05.12.03 23:02
Nej Nico! .. Dt passer ikke.. En virtuel metode kan ikke være statisk..

Spasser ..
User
Bruger #4699 @ 17.12.03 02:00
Spongo> Jo, en virtual metode kan godt være statisk, hvis du deklarerer den over en brometode!!! det vil sige hvis for eksempel over gogle kører et http request som deklarer metoden vedha en søge requester!!!! Ctjek lige dine fackts!!!!!!!!!!
User
Bruger #2165 @ 07.04.05 19:50
Ej hvor plat, du har jo næsten bare oversat afsnittet "A Singleton Class" fra bogen "Game Programming ALL IN ONE", af "Bruno Miguel Teixeira de Sousa"!

Koderne er næsten identiske og din indledning er direkte oversat. Det eneste du selv har skrevet er forklaringerne (som du sikkert også har fået "inspiration" til)!

Jeg er sådan set ligeglad med om du tjener UP på at tage andre folks arbejde. Det der irritere mig, og det gælder for alle, er folk der udnytter andre folks arbejde uden at give dem æren for det. Normalt skal man jo også kontakte dem for at få tilladelse til at bruge deres arbejde og i bogen står der faktisk "All rights reserved"...

Du kunne da i det mindste have givet en kildehenvisning i stedet for at håbe på ikke at blive opdaget...

PS. Jeg beklager hvis jeg tager fejl, for der kan jo være mange der har lavet det på den måde (selvom jeg tog en søgning på google efter de ting der kendetegnede din kode (m_singleton assert) og fandt ikke andre tilsvarende artikler; dem der var benyttede sig af template og var en del anderledes).
User
Bruger #1151 @ 13.07.05 16:10
HAP vil du gerne forklare mig hvornår All right reserved betyder noget? For du siger selv at det "Næsten bare er oversat" og at koder "næsten identiske". Vil det sige at jeg ikke må lave en artikel om operator overloading uden at have læst samtlige bøger først, om der nu er noget som ligner? Brian har sikkert læst den bog som du henviser til, og han har måske også brugt den? Har han lært alt hvad han ved om Singleton klasser fra den bog, er det måske lidt svært at formulere sig anderledes!! Prøv du at have religion i et år, hvor din lærer fortæller dig "sådan her er det" og din bog siger "Sådan her skete det" og så bagefter gå ud og fortælle om det på en helt anderledes måde + at skrive en rapport om det. Du vil få det lige så svært. Brian har ikke lavet kildehenvisninger, det er nok en fejl, men jeg vil vædde med at han ikke bevidst har stjålet fra bogen. Har han lånt noget fra bogen, så var det nok fordi at han selv synes at det beskrev det pågældende del rigtig godt.
User
Bruger #10448 @ 12.11.06 15:10
Jeg er ikke enig med den måde singleton klassen er implementeret på. Den måde som den er implementeret på her i artiklen er ikke korrekt. Den rigtige måde at implementere et singleton designmønster på er som vist her nedenfor.

.h fil:

class Singleton {
public:
static Singleton* Instance();
protected:
Singleton();
private:
static Singleton* _instance;
};

.cpp fil:

Singleton* Singleton::_instance = 0;

Singleton* Singleton::Instance() {
if (_instance == 0) {
_instance = new Singleton;
}
return _instance;
}


Bemærk at singleton objektet's constructor er lavet som protected, så det ikke kan oprettes med denne. Herved undgår man at folk kalder constructoren og derved undgår man unødige fejl og forkert brug af klassen.

En singleton kan hvis det er ønskeligt udvides med en destroyer for at sikre at den bliver slettet igen og undgå leaks. Dette gøres ved at lave en Destroyer klasse og instantiere den som en reference som har en pointer til singleton objektet.

Fremover vil jeg anbefale at du kigger i [GoF] for at finde den rigtige implementation af et designmønster.
User
Bruger #2730 @ 17.06.08 09:54
Efter at være blevet klogere (og ja det kan faktisk godt ske) :) er jeg enig med ovenstående kommentar fra Jess.

Dog er jeg ikke enig i at det er den statiske property "Instance" der skal lave instansen af klassen, derimod er det den statiske constructors opgave [Kig i GOF efter den rigtige måde], da den kun ville blive kaldt een gang, ogderfor vil _Instance altid (i princippet den er ikke trådsikker) altid være 0. Går her ud fra at statiske constructorer findes i C++, har ikke brugt det i 100 år, men kan ikke forestille mig andet.
Du skal være logget ind for at skrive en kommentar.
t