1 Namnkontroll (NameControl) När en ny variabel, ett objekt, en konstant o s v introduceras måste programmeraren namnge denna. Allting identifieras m h a namn. När ett program består av väldigt många komponenter som skrivits av många olika programmerare så är det viktigt att ha ett skydd mot namnkonflikter. Om exempelvis två olika variabler getts samma namn så kommer konstruktion av programmet att misslyckas. 1.1 Namnmatchning (Name lookup) Variabler Namnet för variabler söks i nedanstående ordning 1. Lokalt 2. Klass-scope (inklusive eventuella föräldraklasser) 3. File scope (globalt) void fun(int x) int x = 5; cout << x << endl; cout << x << endl; Funktioner Namn för att matcha en funktion söks i nedanstående ordning. 1. Klass-scope (if invoked from an operation belonging to a class) void X::fun() f(); class X void f(); // name match ; 2. Fil-scope Det är alltid möjligt att själv bestämma aktuellt scope med scope-operatorn :: ::var // access av den globala variabeln var Employee::print() // access av metoden print() tillhörande klassen // Employee
I C++ sker alltid sökande efter ett matchande namn (name match) innan access-kontrollen sker. void f()... //global function class X : public B...; class B private: void f(); // name match ; void X::fun() f(); Den globala funktionen tas inte med som ett matchande alternativ. Access kontrollen avslöjar sedan att funktionen endast är åtkomlig för medlemmar av klassen B. Funktionsanropet är därför illegalt. Om den globala funktionen avses så kan den nås genom ::f();. 1.2 Klass-scope Numreringar, delad data och funktioner Numrerade konstanter kan placeras i klassdeklarationer, detta är en kraftfull mekanism för att undvika namnkonflikter. Namn på globala konstanter är extremt utsatta. class Date public: enum Weekday MON, TUE, WED, THU, FRI, SAT, SUN ; static int days_per_month[12]; //shared data static bool is_leap(int year); //shared function ; int Date:: days_per_month[12] = 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31; //initializing Delad data/delad operation har samma funktion som en global variabel/operation, dock med den viktiga skillnaden att namnet är skyddat av klassen. (static anger att en operation/datamedlem är en delad medlem i klassen. Det innebär att en sådan medlem delas av alla objekt av klassen och kan även anropas utan att ett objekt existerar såvida den är public.) static används endast om man i annat fall skulle ha behövt en global variabel/operation.
bool Date::is_leap(int year) if( year%4!= 0 ) return false; if( year%100!= 0 ) return true; if( year%400!= 0 ) return false; return true; Kan anropas av vem som helst (om public). Delade funktioner har inget implicit argument och kan därför inte accessa klassfält utan att specifiera ett objekt. De kan dock operera på delad data. Till skillnad från en global funktion så kan delade funktioner accessa objekts privata medlemmar under förutsättning att objeket är av den klass som den delade funktionen tillhör. Deklararion av arrayer med konstant storlek i en klass class Stack private: static const int SIZE = 20; // new proposed feature, still an // ERROR with most compilers static const int SIZE; // OK, but this still does not help // array initialization enum SIZE = 20 ; // This is the trick int array[size]; ; // SIZE initieras i implementationsfilen på följande sätt const int Stack :: SIZE = 20;
1.3 Namnutrymmen (Namespaces) Att använda klasser för namnutrymmeskontroll class Vendor_A public: class List... ;... ; class Vendor_B public: class List... ;... ; Löser namnkonflikter Vendor_A::List blir en jobbig referens, prefixen måste vara långa (längre än i detta exempel) för att vara unika. Det är sannolikt att klasserna blir stora, de skall innehålla allt som tillhandahålls av tillverkaren. Det är ineffektivt att parsa stora klassdeklarationer då användaren ofta behöver endast en liten delmängd, detta kan ha signifikant betydelse för tiden det tar att bygga stora program. Namnutrymmesmekanismen 1993 lades en ny mekanism till språket för att tillhandahålla unika namn. Detta är framförallt användbart vid konstruktion av bibliotek. Deklarationer och implementation läggs i ett namespace-block. //Declarations namespace Vendor_A_Containers template<typename T> class List... ; template<typename T> class Queue... ; //Implementation namespace Vendor_A_Containers template<typename T> T Queue<T>::remove()... ; template<typename T> int Queue<T>::length() const... ; Namn är unika om alla tillverkare kan komma överens. För att namn ska vara unika krävs ofta långa namn. Att använda namespaces Vendor_A_Containers::Queue<Employee> q; //exhaustive Metod 1 - Programmeraren kan ge namnutrymmet ett alias. namespace Cont = Vendor_A_Containers;
Cont::Queue<Employee> q; //BETTER! Metod nr 2 class Bank using Vendor_A_Containers :: Queue; private: Queue<Customer> waitingqueue; ; En using-deklaration kan sättas in i vilket scope som helst. Ska sättas in i det scope den behövs. (inte globalt) Ett exempel på användning av namespace För bakåtkompatibilitet med C:s standardbibliotek stöds fortfarande namngivning av headerfiler på formen <xxx.h>. Detta namngivande är nu deprecated. T e x: #include <stdlib.h> //deprecated #include <iostream.h> //deprecated använd den nyare namngivningen, <cxxx>, eller <xxx> istället: #include <cstdlib> //OK, 'c' som ett prefix för gamla c-headers #include <iostream> //OK nuvarande C++ - standard using std::cout; Anledningen till detta är att ".h" headers injicerar alla dess namn i det globala namnutrymmet. Den nya konventionen <cname> eller <name> utnyttjar endast namnutrymmet std. Därför är det nu vanligt att se nyare kod som innehar deklarationen: using std::xx ;
1.4 Accesskontroll Deklarationer och definitioner - generellt I C++ skiljer man på deklarationer och definitioner. En deklaration annonserar en existens av något såsom en funktion, variabel eller klass med ett givet namn och en typ. En definition specificerar implementationen. Variabeldefinitioner allokerar utrymme vilket bereder möjlighet för initiering, funktionsdefinitioner specificerar funktionskod. Definitioner måste vara unika medan deklarationer kan förekomma hur många gånger som helst, även inom samma kompileringsenhet. Exempel på definitioner: int x; Employee lars("lars"); int square(int x) return x*x; class Date //Klassdefinition public: string getdate(); //Operationsdeklaration // // ; Exempel på deklarationer: int square(int x); class Date; enum Color; Definitionerna ovan kan kommas åt av vilken modul som helst om de är globala. För deklarationer och definitioner i en klass gäller givetvis de accesspecifikationer som ges av sektionerna public, protected och private i klassen. Public En deklaration som tillåter access från alla klienter Protected En deklaration som tillåter access från klassen själv, subklasser och vänner (friends). (Denna accessrättighet kommer du att förstå hur den fungerar då du läser om arv. Vi ska inte använda denna accessrättighet.) Private En deklaration som tillåter access från klassen själv och vänner. Friends En klass kan namnge en funktion (icke medlem) eller en annan klass som en friend. Detta ger funktionen eller klassen rättigheter att komma åt även privata medlemmar. Mekanismen är ganska farlig och bör användas med återhållsamhet.
class Node friend class List; friend class Queue; ; private: int _info; Node* _next; Om endast List var en vän till klassen Node skulle man uppnå samma effekt med att ha en nästad klass. Den nästade klassen är i det fallet generellt en bättre lösning. Om två klasser, List och Queue delar på samma länkar kan ej klassen Node nästas in i någon av dem. Lösningen är då att göra Node till en global klass som deklarerar både List och Queue som vänner. Nyckelordet friend säger inte bara att funktionen/klassen är en vän. Implicit betyder detta också att funktionen/klassen inte är en medlem. Nyckelordet friend kan endast användas inuti en klassdefinition. 1.5 Designtips vad gäller namnkontroll Förorena inte det globala namnutrymmet med konstanter. Dessa kan nästan alltid härledas till en klass där de hör hemma. På detta sätt minskas risken för namnkollisioner och problem uppstår endast om två klasser skulle ha samma namn. Eliminera globala variabler. Härled dem till en klass och gör dem till delade variabler i denna. Försök att ändra globala funktioner till delade klassfunktioner. Använd nästade klasser för att definiera implementationsspecifika klasser som är helt ointressanta utanför den klass de är definierade i. Typiska exempel är länkarna i en länkad lista eller noderna i ett träd. Tänk efter noga innan du gör en funktion eller klass till en vän. En klass skall endast göra en annan klass till en vän om den vänskapsgivande klassen är fullständigt underordnad den andra klassen. Exempelvis kan en klass Node, göra klasserna Queue och List till vänner eftersom dess enda uppdrag är att vara informationsbärare åt listor och köer. Använd inte skyddad (protected) data. Vitsen med att inkapsla data (som private) är bl a observationen att representationen av data ofta är föremål för en förändring. Genom att ha data deklarerad som private har man kontroll över de funktioner i en klass som behöver uppdateras om en sådan förändring sker. Skyddad data är åtkomlig av subklasser. En basklass har ej kontroll över vilka andra klasser som ärver från den. Implikationerna av en förändring av skyddad data kan därför inte förutses. Ytterligare en anledning är att en basklass kan kontrollera att dess klassinvarianter åtföljs genom att se till att dess konstruktorer och muterare (funktioner som förändrar klassens data) respekterar dem. Denna kontroll förloras om en ärvande klass får tillgång till basklassens data. Implementationsinvarianter kan vara subtila och det är inte en bra idé att blint lita på att de programmerare som skriver subklasser förstår dem.