C++-arkivet
|
Varför finns referenser?
Vad får detta för konsekvenser? På grund av (1) kan vi förutsätta att det refererade objektet är ok och går att använda. Det finns alltså ingen "null-referens" eller annan motsvarighet till null-pekare. Detta är en fördel eftersom vi dels slipper komma ihåg att jämföra med null när vi skriver vårt program, och dels slipper lägga ner tid på en sådan kontroll vid exekveringstillfället. På grund av (2) måste referensen få sitt rätta värde när den skapas (dvs initieras vid definitionstillfället). Man riskerar därför aldrig att glömma bort att ge referensen ett värde. Kompilatorn hjälper dig med detta. Detta är en fördel eftersom oinitierade variabler ofta leder till fel i våra program som är svåra att hitta. Referens eller pekare?Man kan ge följande rekommendation när du vill skapa ett alias: Se referensen som ditt förstahandsval och ställ dig sedan frågan "Finns det någon bra anledning till att inte använda en referens här?" Förutom "nej" så kan du få två svar på denna fråga:
Här är en sammanfattning av dina valmöjligheter:
Argument till funktionerVi har pratat om att använda pekare och referenser som variabler. Man stöter även på pekare och referenser som argument eller returvärden i funktioner. Vad tänker du när du ser en funktion som tar en pekare som argument? Jag tänker "aha, här kan jag skicka värdet null om jag behöver." En funktion som tar en pekare som argument måste alltid kontrollera så att pekaren inte är null innan den avrefereras! Alltid, alltid, alltid. Om pekaren inte får (eller kan) vara null så skulle funktionen deklarerats att ta en referens istället. Det förekommer att man med kommentarer eller dokumentation säger att "denna pekare får inte vara null", men en referens säger det så mycket tydligare. Man ser här hur viktigt det är med det gränssnitt man exponerar; pekaren kan locka användaren av funktionen att skicka värdet null. Kodexempel 2 innehåller två varianter av funktionen marry. Ovanför marry1 finns en kommentar som säger att fiancee inte får vara null, eftersom man vill undvika giftermål med null-pekare. Men även om du och ditt program följer denna uppmaning idag så kan programkoden ändras av någon annan imorgon och därmed bryta mot förbudet. Då är marry2 mycket tydligare. Med denna version blir ditt program lättare att förstå och därmed att underhålla. Sensmoralen är att du bör skriva din programkod så att det är svårt att göra fel.
God programmeringshygien undviker smittsamma pekareMånga objekt uppstår som pekare, t.ex. vid dynamiskt allokerat minne med new. Bara för att objektet gömmer sig bakom en pekare från början måste du inte skicka runt en pekare. Kolla istället, så fort som möjligt, att pekaren inte är null och paketera sedan om till en referens. Då slipper du jämföra pekaren med null på alla ställen du avrefererar den, vilket skulle påverka ("smitta") resten av ditt program! Ett annat exempel där man ofta ser att pekare "smittar av sig" är i nedanstående kodsnutt (Kodexempel 3):
Klassen House representerar ett hus. Vi antar att ett hus alltid har en ägare, och att den kan byta ägare. Medlemsvariabeln owner är en pekare till ägaren. Vi har valt en pekare eftersom vi måste kunna flytta vårt alias för att representera ett ägarbyte. Vi har två varianter på funktionen getOwner. Den första varianten (getOwner1) har en pekare som returvärde. En pekare kan kännas behändig eftersom owner är en pekare, men vi har då låtit vår interna datarepresentation påverka gränssnittet. Den observante programmeraren som använder funktionen kommer att kolla denna pekare mot null. Därmed spenderar hans program sin tid på meningslösa saker. Vi vet ju att ägaren aldrig kan vara null. Då är getOwner2 tydligare och mer effektiv. Pekarens många ansvarLåt oss diskutera ett exempel med en bil. Antag att bilen kan sakna ägare, t.ex. när den omhändertagits av myndigheter. Nu kan owner vara null. Men man skulle ändå kunna argumentera för att getOwner2 är att föredra! (Det är i och för sig en fråga om tycke och smak, men väl värt att nämna.) Varför referens? Jo, en pekare kan representera andra saker än bara ett alias till ett objekt. En pekare kan representera det första elementet i en array. Den kan även peka till nyallokerat minne med new. Funktioner som returnerar nyallokerat minne är vanligt när man programmerar C (se t.ex. biblioteksfunktionen strdup). Det är dock inget jag skulle rekommendera i C++ där vi har andra valmöjligheter. Så om du exponerar ett gränssnitt där din funktion returnerar en pekare, bli inte förvånad om någon frågar dig "Här får jag en pekare av dig. Varför? Är det möjligen ett nyallokerat objekt? Isåfall, vem äger detta minne?" (Dvs vem ansvarar för att göra delete?) Då är en referens mer okomplicerad: den är alltid bara ett alias för ett giltigt objekt. Om bilen kan sakna ägare måste vi kunna tala om detta. Kom ihåg att referensen inte kan hjälpa oss med detta eftersom det inte finns några "null-referenser". Vi inför en funktion hasOwner. Se Kodexempel 4.
Testning och kontraktAtt använda referenser som i hasOwner/getOwner2 i Kodexempel 4 har även andra fördelar. Oavsett om vi returnerar en pekare (getOwner1) eller om vi returnerar en referens (getOwner2), så måste vi vid något tillfälle avreferera pekaren owner. Vi riskerar då att krascha programmet om det är en null-pekare. Då du testar din kod kan du ha nytta av att ha en assert precis innan du avrefererar pekaren. Då får du så tidigt som möjligt veta om en null-pekare slinker förbi dina kontroller med if. Problemet med getOwner1 är att det kan bli väldigt många ställen med assert. Då är getOwner2 bättre, där behöver vi bara en assert. Se Kodexempel 5.
Användaren anropar hasOwner för att kolla om det finns en ägare. Användaren lovar att inte anropa getOwner2 när ingen ägare finns. Vi kallar detta löfte för ett kontrakt (som i "design by contract"). Som en extra kontroll ser din assert till att kontraktet efterlevs. Under testning stoppar programmet om man anropar getOwner2 och pekaren är null. Observera att alla assert försvinner när du kompilerar din release-version av programmet.
Du väljer själv!I STL (standard template library, en del av standardbiblioteket i C++) har man valt att returnera referenser. Man har bl.a. typen std::vector där man måste kolla om vektorn är tom med empty innan man får ut en referens med t.ex. back. Om man tycker bäst om pekare eller referens bestämmer man själv. Men man bör ha en bra motivering för det val man gör. Lycka till med referenserna!Relaterade artiklar:
|