Operatorer för medlemspekare

Relevanta dokument
Föreläsning 11 Genomgång av inlämningsuppgiften

*:85/ID200V C++ HT07. Föreläsning 8 Medlemspekare Undantagshantering Namnrymder

Föreläsning Mallar

Programmering i C++ EDA623 Objektorienterad programutveckling. EDA623 (Föreläsning 5) HT / 33

Kapitel 6 - Undantag

TDIU01 Programmering i C++

TDIU20 - Objektorienterad programmering i c++ - föreläsning 4

Det finns många flaggor till g++,

Introduktion till arv

Föreläsning 4 Tillägg till C-syntaxen

Tentamen *:85/2I4123 C

Kapitel 3. Synlighet. Kapitel 3 - Klassanvändning, operatorer och pekare. Synlighet

Innehåll. Pekare Exempel

Programmering i C++ EDA623 Arv. EDA623 (Föreläsning 6) HT / 42

Innehåll. Exceptionella händelser (exceptions, undantag ) Felhantering Tre nivåer av felhantering: Nivå 2: exceptions (eller returvärde)

Innehåll. Pekare Exempel

TDIU01 - Programmering i C++, grundkurs

TDIU01 - Programmering i C++, grundkurs

Innehåll. Introduktion till objektorientering. OOP (objektorienterad programmering) Objekt, instanser, klasser

Synlighet. Namespace Scope-operatorn Klasser Vänner

Innehåll. Pekaren this Självreferens. Klasser Resurshantering, representation. Överlagring av operatorer. Överlagring av operatorer

Del3 Klassanvändning, operatorer och pekare Ämnesområden denna föreläsning:

Innehåll. 1 Kort om dynamisk polymorfism. 2 Arv i C++ 3 Multipelt arv. 4 Något om statisk polymorfism. class Container {

Programmering i C++ EDA623 Mallar. EDA623 (Föreläsning 12) HT / 29

Föreläsning 5-7 Operatoröverlagring

Övriga byggstenar. Övriga byggstenar. Några tips under programutveckling. Beroenden Pekare till funktioner Typkonvertering

SP:PROG3 HT12 Tenta

DAT043 - föreläsning 8

DD2387 Programsystemkonstruktion med C++ Tentamen 1 Torsdag 7 januari 2016, 14:00-18:00

Innehåll. Resurshantering. Resource handles. Minnesallokering. Minnesallokering Exempel: allokering på stacken. 6. Resurshantering

C++ Objektorientering - Klasser. Eric Elfving Institutionen för datavetenskap

Innehåll. Resource handles. Resurshantering. Minnesallokering. Minnesallokering Exempel: allokering på stacken. 7. Resurshantering, Felhantering

Operatoröverlagring. endast operatorsymboler definierade i C++ kan överlagras = += -= *= /= %= ^= &= = <<= >>= < > <= >= ==!= && > ->*, [ ] ( )

Lite om felhantering och Exceptions Mer om variabler och parametrar Fält (eng array) och klassen ArrayList.

TDDC76 - Programmering och Datastrukturer

Dynamisk bindning och polymorfism

C++ Objektorientering - Klasser. Eric Elfving

Del2 Klasser, medlemmar och arv Ämnesområden denna föreläsning:

Arrayer. results

Exempel. Arrayer. Lösningen. Ett problem. Arrayer och hakparanteser. Arrayer

grundläggande C++, funktioner m.m.

TDDC76 - Programmering och Datastrukturer

Programsystemkonstruktion med C++

Programmering i C++ EDA623 Mer om klasser. EDA623 (Föreläsning 6) HT / 26

Innehåll. 1 Funktionsmalllar. 2 Klassmallar. struct Name { string s; //... }; const Name & minimum ( const Name & a, const Name & b) { if(a.s < b.

Ett problem. Kontrollstrukturer och arrayer. Arrayer. Lösningen. Arrayer och hakparanteser. Exempel int[] results; results = new int[10]; // 0..

Programsystemkonstruktion med C++: Övning 2. Karl Palmskog september 2010

Funktionspekare, inledning: funktionsanropsmekanismen. Anrop via pekare

Programsystem konstruktion med C++ (2D1387) Innehåll. övning 2 klasser och arv

Tommy Färnqvist, IDA, Linköpings universitet

1 Klasser och objektorientering Vad är objektorientering?

Objektorientering - Arv och polymorfi. Eric Elfving Institutionen för datavetenskap

Innehåll. Typomvandlingar (casting) Implicita Typomvandlingar. Typomvandlingar (type casts) Explicita, namngivna typomvandlingar (C++-11)

Kompilering och exekvering. Föreläsning 1 Objektorienterad programmering DD1332. En kompilerbar och körbar java-kod. Kompilering och exekvering

Klasser. Kapitel 2. Kapitel 2 - Klasser, medlemmar och arv. Klasser. Klasser Medlemmar Arv

LÖSNINGSFÖRSLAG Programmeringsteknik För Ing. - Java, 5p

Ett enkelt program i C++, hello.cpp. #include <iostream> int main() { std::cout << "Hello World\n"; return 0; } C++, Övning 1

C++-programmets beståndsdelar

Tentamen i TDP004 Objektorienterad Programmering Lösningsförslag

5 Arv och dynamisk bindning FIGUR

Innehåll. 1 Typdeklarationer och typomvandling 2 Resurshantering. 3 Objektorientering, kort repetition. 4 Klasser

Byggstenar. C++-programmets beståndsdelar. C++-programmets beståndsdelar. Grundläggande datatyper

2D1387, Programsystemkonstruktion med C++ 01/02 1

Lektionsuppgifter. TDDI14 Objektorienterad programmering. Lektionsplanering Lektion Lektion Lektion

Innehåll. Användardefinierade typer. Användardefinierade typer Kategorier. Konstruktorer. Konstruktorer Två sätt att skriva initiering av medlemmar

TDIU01 - Programmering i C++, grundkurs

Tentamen *:58/ID100V Programmering i C Exempel 3

KLASSER. Inkapsling Abstrakt datatyp Public och private. Klassmedlemmar Datamedlemmar Exempel Funktionsmedlemmar

2D1387 Programsystemkonstruktion med C++ Laboration 1: Grundläggande C++ 31 augusti 2005

Introduktion. Klasser. TDP004 Objektorienterad Programmering Fö 2 Objektorientering grunder

Tentamen EDAF30 Programmering i C++

Idag. Javas datatyper, arrayer, referenssemantik. Arv, polymorfi, typregler, typkonvertering. Tänker inte säga nåt om det som är likadant som i C.

Innehåll. Konstruktorer vid arv Regler för basklassens konstruktor. Konstruktorer vid arv. Konstruktorer vid arv. Konstruktorer vid arv

Innehåll. Parametriserade typer. Klassmallar. Klassmallen Vektor Konstructor med std::initializer_list. Klassmallen Vektor Medlemsfunktioner

Classes och Interfaces, Objects och References, Initialization

TDDI14 Objektorienterad programmering

TDDC76 Programmering och datastrukturer

Funktionens deklaration

Programmera i C Varför programmera i C när det finns språk som Simula och Pascal??

Collections Collections "Envisa" objekt Klasserna Bofstream och Bifstream Definition av metoder i klasserna Bifstream och Bofstream Klassen Streng

1 Namnkontroll (NameControl)

1 Funktioner och procedurell abstraktion

Objektorienterad programmering med Java, Generics

Innehåll. 1 Deklarationer, scope och livstid. 2 Användardefinierade typer. 4 In- och utmatning. 5 Operatoröverlagring. 6 namnrymder (namespace)

Föreläsning 2 Objektorienterad programmering DD1332. Typomvandling

Nedan skapar vi klassen Person innehållande datamedlemmar för förnamn, efternamn, ålder, längd och vikt:

TDDC76 - Programmering och Datastrukturer

Outline. Objektorienterad Programmering (TDDC77) Att instansiera en klass. Objekt. Instansiering. Åtkomst. Abstrakt datatyp.

Innehåll. 1 Deklarationer, scope och livstid. 2 Användardefinierade typer. 4 Operatoröverlagring. 5 In- och utmatning. 6 namnrymder (namespace)

DD2387 Programsystemkonstruktion med C++ Tentamen 2

Det objektorienterade synsättet. Objekt. Datorprogrammet kan uppfattas som en slags modell av den verklighet programmet skall samverka med.

Dagens program. Programmeringsteknik och Matlab. Objektorienterad programmering. Vad är vitsen med att ha både metoder och data i objekten?

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Felsökning. Datatyper. Referenstyper. Metoder / funktioner

Programsystemkonstruktion med C++: Övning 1. Karl Palmskog september 2010

Första exemplet. Kompilator & länkare. Projekt. Övning 1, Ögrupp 4, Programsystemkonstruktion med C++, Ronnie Johansson,

Trädtraversering. Binärt träd. Trädtraversering djupet-först. Trädtraversering bredden-först

Tentamen EDAF30 Programmering i C++

Typkonvertering. Java versus C

F4. programmeringsteknik och Matlab

Föreläsning 5-6 Innehåll. Exempel på program med objekt. Exempel: kvadratobjekt. Objekt. Skapa och använda objekt Skriva egna klasser

Transkript:

Medlemspekare Ibland uppstår behovet att peka ut en viss medlem, som skall behandlas i olika objekt. C++ har begreppet medlemspekare (pointer to member) för sådana situationer (har ingen motsvarighet i Java). En medlemspekare är implementerad som ett offset in i ett objekt till den aktuella medlemmen (hur långt från objektets början medlemmen ligger). För att ge en viss medlem i ett visst objekt måste den kompletteras med ett objekt (namn eller referens) eller en pekare till ett objekt. Medlemspekare till medlemsfunktioner är mest användbara, men först lite synatx. Exempel: antag deklarationerna struct Strukt{ int x, y; ; Strukt vek[10]={{2,3, {5, 17, {3, 8, ; Antag att vi vill kunna summera ibland alla x i vek, ibland alla y: int Strukt::*mpek; // Deklaration av medlemspekare till en int i Strukt if (summera x) mpek=&strukt::x; // Medlemsadress -tagning else mpek=&strukt::y; int sum=0; for(int i=0; i<10; i++) sum += vek[i].*mpek; // Åtkomst via medlemspekaren Bild 121 Operatorer för medlemspekare C++ inför tre nya operatorer för medlemspekare och en notation för medlemsadress -tagning: ::* - deklarator för medlemspekare, t.ex. int Strukt::*mpek; Obs alltså att medlemmarnas typ och klassens namn ingår. Medlemsadress - tagning - Adressen till en medlem fås genom notationen mpek=&strukt::x; Obs dock att det är en specialkonstruktion, i själva verket står en medlemspekare för ett offset, inte för en adress. Detta offset måste kombineras med en pekare till ett objekt eller med ett objekt (eller en referens till ett objekt) ->* - åtkomst till en medlem via medlemspekare hos ett objekt som pekas ut av en objektpekare, t.ex. int i=pek->*mpek;.* - åtkomst till en medlem via medlemspekare hos ett objekt (eller en objektreferens), t.ex.: Strukt obj={5, 7; int i=obj.*mpeki; Bild 122 1

Pekare till medlemsfunktioner Exempel: class Valued{ int value; Valued():value(0){ void add(int v){value+=v; void sub(int v){value-=v; void mult(int v) {value*=v; ; Valued values[10]; void (Valued::*mfunk)(int i); //Pekare till medlemsfunktion cout << Operation värde: char perator; int perand; cin >> perator >> perand; switch(perator){ case + : mfunk=&valued::add; break; case - : mfunk=&valued::sub; break; case * : mfunk=&valued::mult; break; for(valued *pek=values; pek<values+10; pek++) (pek->*mfunk)(perand); Bild 123 Mer om medlemsfunktionspekare Medlemsfunktionspekare kan t.ex. läggas i datastrukturer som objekt, map<>, arrayer: void (Valued::*mfarr[3])(int) = {&Valued::add, &Valued::sub, &Valued::mult; Varvid både objektet och operationen kan indexeras: (values[5].*mfarr[1])(3); // Anropar values[f].sub(3); Definitionen av arrayen kan förenklas med typedef: typedef void (Valued::*Valfunk)(int); Valfunk mfarr[3]={&valued::add, &Valued::sub, &Valued::mult; Oftast görs sådana typnamndefinitionen i klassen varvid klassnamnet syns i arraydefinitionen: class Valued{ typedef void (Valued::*Mfunk)(int); ; Valued::Mfunk mfarr[3]={&valued::add,&valued::sub,&valued::mult; Bild 124 2

Användning av medlemsfunktionspekare Den viktigaste användningen av medlemsfunktionspekare är för sammankoppling av bibliotekskomponenter med medlemsfunktioner i en tillämpnings objekt. Jämför med Javas grafiska komponenter: man installerar s.k. lyssnare som måste ha metoder med föreskriven signatur (namn och argumenttyper), t.ex. void actionperformed(actionevent); Om tillämpningens objekt behöver reagera olika på olika händelser av denna typ och behöver ha flera sådana metoder tvingas man införa nya klasser (oftast inre klasser; s.k. lyssnarklasser) eftersom en klass inte kan ha flera metoder med samma signatur. I C++ med användning av medlemsfunktionspekare kan man skapa direkta kopplingar mellan bibliotekskomponenter och olika medlemsfunktioner i objekt av en tillämpningsklass, dessa medlemsfunktioner behöver inte ha något föreskrivet namn. Eftersom deklarationen av en medlemspekare måste innehålla namnet på tillämpningens klass där medlemsfunktionen ingår måste en sådan koppling i ett bibliotek göras som en mall. Vi återkommer till hur detta görs. Bild 125 Orientering om undantagshantering Undantagshantering i C++ liknar mycket Javas, som lånat mekanismen och syntaxen från C++. Den är dock inte lika genomgripande som i Java - bl.a. kunde man inte införa run-time-kontroller p.g.a. kravet på kompatibilitet med C. Undantag genereras av biblioteket och run-time-systemet endast i ett fåtal fall, indexering utanför arraygränser eller avreferering av null-pekare ger inga undantag utan eventuellt ett vanligt exekveringsavbrott ( Segmentation fault ). Viktigaste skillnader mot Java: egna undantagsklasser används (dock finns en liten bibliotekshierarki) vid throw används inte new, man skapar temporära objekt vid catch behöver inte argumentet namnges argumenttypen till catch bör deklareras som referens att fånga alla undantag görs med catch() det finns ingen motsvarighet till finally undantagsspecifikationer är inte obligatoriska i en undantagshanterare kan throw skrivas utan argument, innebär att samma undantag skickas vidare (re-throw) Bild 126 3

Standardbibliotekets undantagshieraki Felaktiga argumentvärden m.m., programmeringsfel logic_error exception Andra fel som upptäcks under exekveringen runtime_error length_error out_of_range bad_alloc bad_exception domain_error invalid_argument bad_cast ios_base::failure range_error overflow_error underflow_error Klasserna exception och bad_exception finns i headerfilen <exception>, de flesta andra i <stdexcept>, men ios_base::failure i <ios> Bild 127 Standardbibliotekets undantagshieraki, forts. I klassen exception deklareras virtual const char *what() const; som subklasserna är tänkta att överskugga för att returnera en C-sträng med ett meddelande som kan skrivas ut. Konstruktorerna i logic_error och runtime_error kräver en sträng (std::string) med ett meddelande som kommer att lagras i undantagsobjektet för att returneras vid efterföljande what()-anrop. length_error en längdangivelse skulle överskrida max domain_error värde av fel matematisk domän out_of_range värde utanför tillåtet intervall (t.ex. vid indexering) invalid_argument otillåtet funktionsargument bad_alloc inget minne vid dynamisk allokering bad_exception odeklarerat undantag från en funktion (se nästa bild) bad_cast misslyckad dynamic_cast för referens ios_base::failure misslyckad iostream-operation (måste begäras) range_error intervallfel i interna beräkningar overflow_error aritmetiskt overflow underflow_error aritmetiskt underflow Bild 128 4

Undantag: syntaktiskt exempel #include <stdexcept> class Text{ int size; char *cptr; char& operator[](int index){ if (index < 0 index >= size) throw std::out_of_range( Text index error ); return cptr[index]; ; #include Text.h #include <iostream> using namespace std; int main(){ Text t( Eberhart von Ostenbrink ); try{ cout << Vilken position: ; int pos; cin >> pos; cout << t[pos]; catch(out_of_range& oor){ cerr << Fel: << oor.what() << endl; Bild 129 Ofångna undantag Om ett ofånget undantag når main() (och inte fångas upp där heller) anropas funktionen std::terminate() som avbryter programmet genom att anropa abort() Man kan sätta en egen termineringsfunktion (argumentlös funktion med void som returtyp, (void (*)())) med std::set_terminate(void (*handler)()). En termineringsfunktion förväntas avbryta programmet (annars anropas abort() ändå). Bild 130 5

Undantagsspecifikationer En funktion eller konstruktor kan deklarera att den kan generera vissa undantag: void funk(string str,int i) throw(bad_alloc, range_error); Undantagsspecifikationer är en del av funktionens signatur och måste upprepas i definitionen (om man har både en deklaration och en definition). Eftersom undantagsspecifikationer lades till C++ när språket redan användes kunde man inte göra undantagsspecifikationer obligatoriska. Istället är det så att en funktion utan undantagsspecifikation kan generera alla undantag. För att deklarera att en funktion inte tänks generera några undantag skriver man: void funk(string str, int i) throw(); Om en funktion med en undantagsspecifikation genererar ett odeklarerat undantag sker ett anrop till std::unexpected() som i sin tur anropar std::terminate(). Om funktionen innehåller bad_exception i sin undantagsspecifikation genereras istället detta undantag. Dessutom kan man sätta en egen funktion (void (*)()) som unexpected genom anrop till std::set_unexpected(void (*handler)()). Bild 131 Undantagshierarkier Liksom i Java brukar man samla undantag som hör ihop i en klasshierarki: #include <stdexcept> struct Stack_error:public std::length_error{ Stack_error(const std::string& msg):std::length_error(msg){ ; struct Stack_full:public Stack_error{ Stack_full(const std::string& msg):stack_error(msg){ ; struct Stack_empty:public Stack_error{ Stack_empty(const std::string& msg):stack_error(msg){ ; class Stack{ int data[100]; int count; Stack():count(0){ void push(int value) throw(stack_full){ if (count == 100) throw Stack_full("Stack full"); data[count++]=value; int pop() throw(stack_empty){ if (count == 0) throw Stack_empty("Stack empty"); return data[--count]; Bild 132 ; 6

Undantagshierarkier Vad man uppnår genom att gruppera undantagen är att tillämpningar kan fånga undantagen med basklassnamnet (om de inte är intresserade av vilket specifikt undantag som genererades). Motsvarande gäller undantagsspecifikationer. Liksom i Java undersöks catch-fraserna uppifrån och ner, vill man ha en speciell hantering av ett visst undantag och mer generell hantering av övriga undantag så måste de specifika undantagstyperna stå först: Stack stack; try{ for(int i=0; i<x; i++) stack.push(i); for(int i=0; i<y; i++) cout << stack.pop(); catch(stack_full&){ cerr << För många tal! << endl; catch(stack_error&){ cerr << Fel vid stackhantering! << endl; catch(length_error&){ cerr << För mycket eller för lite av något! << endl; catch(){ cerr << Ett fel har inträffat! << endl; Bild 133 Resurshantering vid risk för undantag m.m. Viktigt att komma ihåg är att det inte finns någon automatisk garbage collection i C++. Detta gör att om en funktion allokerar minne dynamiskt i början med avsikt att friställa minnet i slutet, men kan avbrytas däremellan av undantag så sker ett minnesläckage. Även i andra sammanhang (öppna / stänga filer o.s.v.) kan det hända att en avslutningsoperation inte utförs vid undantag. Ett bra sätt att undvika detta är att resursanskaffning sker i konstruktorn till ett lokalt objekt och återlämnande sker i objektets destruktor. Vid eventuellt undantag städas objektet bort, dess destruktor anropas och kan städa efter anropet. Speciellt för minnesallokering kan stdandardbibliotekets auto_ptr<> från headerfilen <memory> användas istället för pekare: void funk() throw(bad_alloc, range_error) {. auto_ptr<person> pers(new Person( Ulrika )); Bild 134 7

Undantag och konstruktorer/destruktorer En konstruktor som upptäcker sådana fel i sina argumentvärden att den inte kan konstruera ett meningsfullt objekt kan inte göra annat än generera ett undantag: // Rational.h #include <stdexcept> class Rational{ int num, den; explicit Rational(int n=0, int den=1) throw(std::invalid_argument); // Rational.cpp #include Rational.h using namespace std; Rational::Rational(int n, int d) throw(invalid_argument):num(n), den(d){ if (d == 0) throw invalid_argument( Rational: zero denominator! ); reduce(num, den); En destruktor å andra sidan får inte generera undantag det kan hända att den är anropad under uppstädning av stacken pga ett annat undantag. Om detta inträffar anropas terminate()-funktionen. Bild 135 Deklarationsvidder Namn i ett program är deklarerade i olika deklarationsvidder (scope). En deklarationsvidd är ett textuellt område i programmet där namnet är känt av kompilatorn och bundet till det det är namn på (en variabel, en funktion, en typ). Obs att deklarationsvidder är en statisk, textuell gruppering av namn som bara har betydelse i källkoden och inte överlever kompilering. I C++ finns följande typer av deklarationsvidder: den globala vidden namnrymder klasser funktioner block Bild 136 8

Deklarationsviddsoperatorn :: Om man från utanför en deklarationsvidd vill referera till ett namn deklarerat inom en deklarationsvidd använder man viddens namn följt av deklarationsviddsoperatorn :: och det namn man vill använda (gäller inte funktioner och block, namn deklarerade i dessa vidder existerar inte utanför vidderna): std::cout << Hej hopp! << std::endl; Obs att viddens namn och :: skrivs närmast namnet, även om de ingår i ett mer sammansatt uttryck: pek->person::get_name(); Om man i en deklarationsvidd vill använda ett namn från den globala vidden som gömts av ett lokalt deklarerat namn kan man använda :: utan namn på vidden: int x=13; void funk(){ int x=173; cout << ::x; // Det globala x, 13 skrivs ut En deklarationsviddsoperator finns även i Java, men den ser ut på samma sätt som medlemsåtkomstoperatorn (alltså en punkt). Bild 137 Namnrymder Namnrymder utgör ett enkelt sätt att gruppera namn på typer (t.ex. klasser), funktioner o.s.v. som hör ihop inom ett namn: #ifndef TEXT_H #define TEXT_H #include <iostream> namespace mylib{ class Text{ Text(const char *str); int length() const; // o.s.v. ; // end of class Text std::ostream& operator<<(std::ostream&, const Text&); // end of namespace mylib #endif På det sättet uppnår man en naturlig gruppering av namn som hör ihop och undviker olösbara namnkonflikter i tillämpningar som kanske använder andra bibliotek som också deklarerar namnet Text (med en annan betydelse) Namnrymder motsvaras närmast av package i Java, men har i C++ ingen betydelse för medlemmarnas åtkomstskydd. Det finns inte heller några krav på header-/källkodsfiler, kataloger o.s.v. Bild 138 9

Namnrymder, forts 1. Vid definition av ett namn som deklarerats inom en namnrymd kan varje namn kvalificeras med namnrymdens namn för sig: #include Text.h #include <cstring> mylib::text::text(const char *str): size(std::strlen(str)+1), cptr(new char[size]){ std::strcpy(cptr, str); int mylib::text::length() const { return size; Men namnrymder är öppna, man kan introducera definitionerna och även deklarera nya namn i samma namnrymd: #include Text.h #include <cstring> namespace mylib{ Text::Text(const char *str): size(std::strlen(str)+1), cptr(new char[size]){ std::strcpy(cptr, str); int Text::length() const { return size; Bild 139 Namnrymder, forts. 2 Namnrymder är som sagt öppna, man kan deklarera nya namn i samma namnrymd (på samma sätt som man i Java kan deklarera flera klasser som hörande till samma package): #ifndef KLOCKA_H #define KLOCKA_H #include <iostream> namspace mylib{ class Klocka{ // o.s.v. ; std::ostream& operator<<(std::ostream&, const Klocka&); #endif Namnrymder bör användas för grova grupperingar av namn. Vid utveckling av ett bibliotek bestående av många klasser och hjälpfunktioner borde hela biblioteket göras till en namnrymd eller ett fåtal namnrymder. Bild 140 10

Anonyma namnrymder För att göra vissa namn privata för en modul (källkodsfil) kan man använda anonyma namnrymder: namespace{ // hjälpfunktioner, privata för modulen void helper1(.) {. void helper2(.) {. void funk() { helper1(); Inom källkodsfilen blir namnen från den anonyma namnrymden tillgängliga utan någon kvalifikation, utanför denna källkodsfil är de otillgängliga. Detta ersätter C:s deklarationer av globala namn som statiska för att gömma dem för andra moduler (mekanismen finns kvar, men bör inte användas). Bild 141 using-direktiv och using-deklarationer Med ett using-direktiv (using namespace ) som används med namnrymder öppnar man hela namnrymden, som om alla namn i namnrymden lades in i den globala namnrymden, t.ex. #include <iostream> #include <string> using namespace std; Med using-deklarationer hämtar man in ett visst namn från en annan deklarationsvidd till en lokal deklarationsvidd, t.ex. #include <iostream> #include <string> void funk(){ using std::string; using std::cout; string namn = Jozef ; cout << namn << std::endl; Bild 142 11

Namnrymder: namnuppslagning, alias Begrunda följande kod: #include <iostream> #include <string> //Inget using namespace std-direktiv int main(){ std::string namn( Jozef ); std::cout << namn; // Var finns operator<<(ostream&, string)? Om kompilatorn inte hittar den anropade funktionen i den lokala namnrymden letar den efter funktionen i de namnrymder där argumenttyperna är definierade (Koenig lookup). För att man inte skall dra sig för att ge namnrymder meningsfulla, långa namn kan man skapa alias till namnrymder: namespace star85_library_at_dsv_ht04{. namespace s85= star85_library_at_dsv_ht04; Bild 143 Inluppen: exempel på en liten tillämpning Vandring över skärmen med piltangenterna När markören hamnar över en knapp visas knappen med omvänd färg. Om man då trycker på RETURN utförs handlingen (värdet i displayen ökas eller minskas med 1, eller programmet avslutas). Komponenterna behöver inte vara inramade Längst ner en meny: vänster/högerpilar byter alternativ, RETURN utför handlingen. Om markören finns inom menyn fungerar tangenterna +, - och q som kortkommandon. Obs! Exemplet är mycket enkelt! Bild 144 12

Ett större exempel Komponenterna är inte inramade, spänner över hela fönsterbredden. Lägg märke till menyns utformning. Bild 145 Samma exempel, annan bildskärmsbild Även i Pine beror tangentkommandons betydelse på var markören befinner sig. Scrollande delfönster o.s.v. är inte så svåra att göra, men de ingår inte i den obligatoriska delen av uppgiften Bild 146 13

Åter till det lilla exemplet: skiss på tillämpningsprogrammet class Value{ int val; void oka() { val++; // Och visa nya värdet! void minska() { val--; // Och visa nya värdet! ; int main(){ Value v; Session ses; // Skapa displayen så att v kan visa värdet där // Skapa knappen för öka, koppla till v.oka // Skapa knappen för minska, koppla till v.minska // Skapa knappen för sluta, koppla till exit // Skapa menyn // Skapa menyvalet +, koppla till v.oka // Skapa menyvalet -, koppla till v.minska // Skapa menyvalet q, koppla till exit // Skapa kortkommandot +, koppla till v.oka // Skapa kortkommandot -, koppla till v.minska // Skapa kortkommandot q, koppla till exit ses.run(); Tillämpningen anger vid skapandet av skärmobjekten var på skärmen de skall vara, vad som skall stå i dem o.s.v. Bild 147 Inlämningsuppgiften Skapa (början till) ett klassbibliotek med komponenter för sådana tillämpningar Obligatoriska komponenter: etikett (label), knapp, editerbar sträng, meny, sammansatt komponent session Bestäm själva detaljutformningen (inramade komponenter eller ej, eller både och, färganvändning osv) Biblioteket skall vara förberett för utbyggnad, dvs utformat mer generellt än vad som krävs just här För användaren (tillämpningsprogrammeraren): bekvämt men flexibelt, dvs implementerade defaultbeteenden men möjlighet att ändra Internt: hög grad av uppdelning, varje klass sköter sina naturliga uppgifter Bild 148 14

Tilläggskrav på implementering Datasamlingar implementerade med STL-behållarklasser Genomgångar, sökningar osv med generiska algoritmer från standardbiblioteket Adaptorer för funktionsobjekt (bind1st, bind2nd, mem_fun osv) används där det är möjligt, egna funktionsobjekt skapas annars. Kopplingar till tillämpningsklassers metoder sker genom medlemsfunktionspekare. Komponentobjekten hanteras via referensräknande pekarklasser. Biblioteket reagerar på felaktigheter (överlappande komponenter, komponenter utanför skärmarean osv) genom att generera undantag. Icke-triviala medlemsfunktioner definieras utanför klassdefinitioner (gäller även mallar). Deklarationer av klasser i headerfiler, definitioner av medlemsfunktioner och hjälpfunktioner i implementeringsfiler (gäller inte mallar eller infogade (inline) funktioner). Hög grad av uppdelning i filer - helst varje klass på egen fil (använd make!). Bild 149 Förenklingar och önskemål Förenklingar: Icke-överlappande komponenter Önskemål: Kontroll av vilken komponent markören befinner sig i, kontroll av om komponenter överlappar varandra, eventuell beräkning av position för centrering av text osv är operationer som har att göra med rektanglar. Skapa gärna en klass rektangel med denna funktionalitet som sedan kan användas i komponentklasserna, gröta inte ner komponenterna eller sökfunktionerna med koordinataritmetik. Bild 150 15

En möjlig arkitektur för exemplet (instansexempel) Session getcont setcont run content add remove add Knapp öka Knapp minska Knapp sluta Menyvalen Display Meny + - q q + - main v 7 öka minska Ett tillämpningsobjekt Bild 151 Rectangle Förslag till grov klasstruktur contains Passive handle_event Component show erase enter leave handle_event Edit_string Button Compund show show show handle_event handle_event handle_event add get_text add remove Menu show handle_event add add Label show set_text Session getcontent setcontent run Action_base perform Action<> Ptr<> * -> perform Bild 152 16

Grov beskrivning av klasser Rectangle innehåller skärmkoordinater för en rektangulär area och medlemsfunktioner för koordinataritmetik, t.ex. contains() som tar en punkt (kolumnoch radnummer) och returnerar om denna punkt ligger inom dess area. Är mest till för hålla koordinataritmetik separerad från den övriga koden för komponenterna. Component den egentliga rotklassen i hierarkin. Borde innehålla en rent virtuell medlemsfunktion show() som implementeras i subklasserna och ritar upp komponenten på skärmen; en rent virtuell medlemsfunktion handle_event() som tar en Event-struct (så att komponenten kan se vad det var för tangent och var den trycktes) och returnerar ett booleskt värde (true om komponenten hanterat händelsen, false annars); två virtuella funktioner enter() och leave() som borde vara implementerade som tomma funktioner men kan överskuggas av subklasser om man vill att något speciellt skall hända när markören flyttas in i komponentens område resp. flyttas ut därifrån. Innehåller även icke-virtuell funktion erase() Passive rotklassen för passiva komponenter, kan implementera handle_event()- medlemsfunktionen så att den alltid returnerar false - passiva objekt reagerar inte på några händelser Bild 153 Label Grov beskrivning av klasser, forts. lövklass, implementerar show() som skriver ut texten på skärmen Edit_string lövklass, editerbar sträng som implementerar handle_event(): den reagerar på skrivbara tecken genom att lägga in dem i en intern sträng och skriva ut den förändrade strängen på skärmen samt även på delete-tangenten och rubout-tangenten genom att ta bort tecken ur strängen. Den implementerar även show() och har en egen medlemsfunktion get_text(). Denna klass borde implementeras sist. Button lövklass, implementerar show() och handle_event(): den reagerar på RETURNtangenten genom att anropa någon medlemsfunktion i något objekt av någon klass i tillämpningen. Måste alltså ha en koppling till tillämpningen (en Action_base *, se nedan Action och Action_base). Bild 154 17

Grov beskrivning av klasser, forts. Compound klassen för sammansatta skärmobjekt. Borde innehålla en datasamling för de ingående skärombjekten (lämpligen en vector med pekare till skärmobjekt) och en datasamling med kortkommandon (lämpligen en map med tangentkoder som nycklar och Action_base-pekare som värden - se Action och Action_base). Borde ha två funktioner add(): en för att addera komponenter och en för att addera kortkommandon, samt funktionen remove() för borttagning av komponenter. Implementerar handle_event() genom att gå leta upp den komponent inom vars area händelsen inträffat och anropa dess handle_event(). Om komponenten svarar att den inte hanterar denna händelse (eller om händelsen inträffat utanför någon ingående komponent) så kontrolleras om detta objekt självt kan hantera händelsen (t.ex. genom att titta i kortkommando -samlingen). Medlemsfunktionen show() implementeras genom att gå igenom de ingående komponenterna och anropa deras show(). Bild 155 Grov beskrivning av klasser, forts. Menu egentligen ett sammansatt objekt vars ingående komponenter endast kan vara knappar. Dessutom borde piltangenter flytta markören till nästa/föregående knapp och knapparna borde ligga bredvid varandra. Många tyckte att Meny borde vara en subklass till Compound, men det borde den inte alls. Ett subklassobjekt skall kunna användas i alla de sammanhang där ett basklassobjekt förväntas - till ett Compound-objekt kan man addera godtyckliga komponenter, men det kan man inte till ett Menu-objekt. Däremot borde Menu implementeras genom att innehålla ett Compound-objekt och de flesta medlemsfunktioner i Menu blir nog bara till anrop av motsvarande funktion i det inneslutna Compound-objektet. En bra sak vore om add-funktionen för knappar i Menu inte tog en färdigskapad knapp utan bara info om vad som skall stå på knappen och vad den skall kopplas till, då kan Menu-objektet räkna ut vilka koordinater nästa knapp i menyn skall ha och skapa knapp-objektet självt, innan det skickas vidare till add() i det inneslutna Compund-objektet. Några design-beslut om hur menyerna skall se ut (stående, liggande, knappstorleken, hur kan tillämpningen styra detta) måste göras i så fall. Bild 156 18

Grov beskrivning av klasser, forts. Action representerar en koppling mellan biblioteksklasser och tillämpningen. Action_base Innehåller en pekare till ett objekt av en tillämpningsklass, en medlemspekare till en medlemsfunktion i denna klass samt medlemsfunktionen perform() som anropar denna medlemsfunktion hos detta objekt. Eftersom tillämpningsklassen är okänd måste Action vara en mall. Eftersom man vill kunna ha objekt av olika instansieringar av denna mall i samma datasamling (samlingen av kortkommandon i ett Compound-objekt) så bör man skapa en basklass (som inte är en mall) till Action, nämligen Action_base. Denna basklass borde innehålla en virtual-deklaration av medlemsfunktionen perform(). I de övriga biblioteksklasser där man vill ha en koppling till tillämpningars objekt och medlemsfunktioner kan man nu ha en pekare till Action_base, som alltså kan peka ut objekt av olika instansieringar av Action. Den föreslagna klassen Action representerar en förbindelse med en medlemsfunktion i ett objekt. Man kan tänka sig att en tilläpmning även skulle vilja kunna skapa en koppling mellan bibliotekskomponenter och fristående funktioner i tillämpningen. Vill man förbereda för det kan man göra en specialisering av Action-mallen där typen man specialiserar för är en funktionspekartyp och där Action-klassen innehåller en funktionspekare istället för en objektpekare och en medlemsfunktionspekare. Bild 157 Grov beskrivning av klasser, forts. Session representerar hela terminalskärmen. Innehåller ett sammansatt objekt som i sin tur innehåller skärmobjekten. Har medlemsfunktionerna getcontent() för att returnera en pekare till det sammansatta objektet och setcontent() för att byta ut det mot ett annat. Tillämpningen skapar ett Session-objekt, adderar komponenter och kortkommandon till dess Content och anropar sedan funktionen run(). run() innehåller en loop där man läser in nästa händelse och skickar denna händelse till rätt komponent. Förutom händelseinläsning är gången precis som för ett sammansatt objekts händelsehantering. Bild 158 19

Generiska enheter - mallar (templates) Upprepning En mall i C++ är ett källkodsmönster för en klass- eller funktionsdefinition, där någon eller några specifika typer (eller värden) har ersatts med formella parametrar. Vid användning av mallen anges vika aktuella typer som skall användas, varvid kompilatorn genererar motsvarande klass- eller funktionsdefinition och kompilerar den (instansiering). Denna mekanism kan (speciellt i C++) liknas med en preprocessor som gör en textuell ersättning av de formella parametrarna med aktuella argument. Kompilatorn förstår dock mallar och kan i viss mån kontrollera deras syntax. Mekanismen kan även ses som en ökning av abstraktionsnivån: klasser utgör abstraktioner över objekt, men klassmallar utgör abstraktioner över klasser. Andra viktiga språk som understödjer generiska enheter är t.ex. Ada och Eiffel. Det finns även varianter av Java med generiska enheter, t.ex. GJ (Generic Java) Genom mallar stödjer C++ generisk programmering som är en annan programmeringsparadigm än objektorienterad programmering och erbjuder i många fall alternativa sätt för lösning av samma typer av problem. Bild 159 Abstraktionsnivåer Mallar Klasser (eller funktioner), alltså typer Objekt (eller anrop) Bild 160 20

void swap(typ& x, TYP& y){ TYP slask=x; x=y; y=slask; Funktionsmallar - exempel Mest upprepning Ur denna funktionsmall kan kompilatorn framställa funktionsdefinitioner för byte mellan variabler av godtycklig (men samma) typ för vilken tilldelning är tillåten. Genereringen av en funktions- eller klassdefinition ur en mall för ett visst mallargument kallas instansiering av mallen. Den genererade definitionen kallas en specialisering. Instansieringen för funktioner är implicit - man behöver (oftast) inte ange de aktuella mallargumenten. När kompilatorn ser anropet av en funktion med detta namn gissar den mallargumenten från argumenttypen i anropet: int main(){ int i1=13, i2=127, i3=173, i4=14; double d1=3.5, d2=7.3; swap(i1, i2); // swap(int&, int&) genereras swap(d1, d2); // swap(double&, double&) genereras swap(i3, i4); // inget genereras, swap(int&, int&) finns redan, anropas swap(i1, d1); // Fel, det finns ingen mall för swap med olika // argumenttyper Bild 161 Klassmallar - exempel: mall för en vektorklass #ifndef VECTOR_H #define VECTOR_H class Vector{ int siz, cap; TYP *arr; Vector():siz(0), cap(10), arr(new TYP[cap]){ Vector(const Vector& other); // Andra konstruktorer, destruktorn, tilldelning TYP& operator[](int pos){ return arr[pos]; const TYP operator[](int pos) const { return arr[pos]; void push_back(const TYP& val); void pop_back(){ siz--; int size() const { return siz; TYP max() const; // Andra vektoroperationer ; Som för vanliga klasser kan medlemsfunktioner definieras utanför klassdefinitionen, men Bild 162 21

Vector<TYP>::Vector(const Vector& other): siz(other.siz), cap(other.cap), arr(new TYP[cap]){ for(int i=0; i<siz; i++) arr[i] = other.arr[i]; template <class T> void Vector<T>::push_back(const T& val){ if (siz==cap){ T *tmp=new T[cap*=2]; for(int i=0; i<siz; i++) tmp[i]=arr[i]; delete [] arr; arr=tmp; arr[siz++]=val; TYP Vector<TYP>::max() const { if (siz == 0) throw std::length_error( Fel ); TYP m = arr[0]; for(int i=1; i<siz; i++) if (arr[i] > m) m = arr[i]; return m; #endif Mallar för definitioner av medlemsfunktioner eftersom de är medlemmar i en mall så blir deras definitioner också mallar! Dessutom måste även definitionerna finnas med i headerfilen - mer om detta senare. Obs att mallparameterns namn har bara betydelse lokalt i varje mall Bild 163 Användning av vektormallen i en tillämpning #include Vector.h // Säg att Vector-mallen finns här #include string using namespace std; class Person{ ; int main(){ Vector<int> ivec; Vector<string> svec; ivec.push_back(13); svec.push_back( Stefan ); String sm = svec.max(); int im = ivec.max(); Vector<Person> pvec; pvec.push_back(person( Jozef, 53)); pvec.push_back(person( Stefan, 61)); Instansiering av klassmallar är explicit - mallargumenten måste anges. Medlemsfunktioner instansieras först vid kompilering av ett program som anropar dem! Person pm = pvec.max(); // Fel, instansiering av // Vector<Person>::max // misslyckas, det finns ingen operator> // för Person! Bild 164 22

Obegränsad genericitet I många mallar måste man anta att vissa operationer kommer att vara definierade för de typer mallen kan instansieras för. Det finns inget sätt i C++ att ange sådana restriktioner för de typer mallen skall kunna instansieras för. Instansieras mallen i tillämpningen med en typ som saknar någon operation mallen förutsätter så blir det kompileringsfel vid instansiering av den (ev. medlems-) funktion som använder operationen, med felmeddelanden gällande den felaktiga koden i mallen, inte i tillämpningen. Å andra sidan kan man använda sådana mallar med fel typer så länge man inte använder de medlemsfunktioner som förutsätter den saknade operationen. Vi skall senare se hur man kan parametrisera mallar även med operationer. I andra språk som använder genericitet brukar man kunna ange vilka operationer som måste vara definierade för typer mallen instansieras för. I dessa språk får man alltså felmeddelanden vid instansiering av mallar med fel typer. Bild 165 Mer om klassmallar Obs! att i och med att klassdefinitionen är en mall så blir definitioner av dess medlemsfunktioner också mallar. Det finns nu ingenting som heter Vector, överallt där man vill använda mallen måste denna instansieras, antingen med en specifik typ, t.ex. Vector<int> eller med mallargumentet till den enhet som använder Vector<TYP>, alltså måste denna enhet också vara en mall: void printvector(const Vector<TYP>& vec){ for(int i=0; i<vec.size; i++) cout << vec[i] << endl; // Förutsätter att << är definierad // för TYP Obs att mallighet är smittsam : en funktion som skall kunna ta som ett argument en godtycklig specialisering av en mall måste själv vara en mall, om funktionen är en medlemsfunktion i en klass så måste klassen vara en mall (inte riktigt sant, se senare om medlemsfunktionsmallar), en klass som skall ha som medlem en godtycklig specialisering av en klassmall måste själv vara en mall o.s.v. Bild 166 23

Mer om mall- och typnamn Inuti en klassmall kan dock namnet utan mallargumenten, t.ex. Vector, användas som en förkortning för en instansiering med samma argument, t.ex. Vector<TYP> Exempelvis är definitionen av copy-konstruktorn egentligen Vector<TYP>::Vector<TYP>(const Vector<TYP>& other): siz(other.siz), cap(other.cap), arr(new TYP[cap]){ for(int i=0; i<siz; i++) arr[i] = other.arr[i]; Deklarationer med klassmallar kan bli ganska grötiga, man använder därför ofta typedefs för specialiseringar man använder frekvent: typedef Vector<int> Intvector; typedef Vector<string> Strvector; T.ex. är standardbibliotekets string egentligen bara en typedef: typedef basic_string<char, char_traits, alloc<char> > string; Den genererade klassen har inte längre tillgång till namnet på den typ mallen har specialiserats för. Om man vill kunna få fram denna typ kan man lägga in en typedef i mallen: class Vector{ typdedef TYP value_type; Vi kommer senare att se behovet av sådana konstruktioner Bild 167 Definitioner i headerfiler! Eftersom mallar instansieras först vid kompilering av program som använder dem måste deras definitioner finnas tillgängliga för kompilatorn vid kompilering av tillämpningsprogrammet. Detta innebär att definitioner av funktionsmallar och medlemsfunktionsmallar måste finnas i headerfiler (de följer samma regler som inline-funktioner). Det hindrar inte att man fortfarande gör (mallar för) klassdefinitioner först och placerarar (mallar för) medlemsfunktioner därefter, utanför (mallen för) klassdefinitionen. Om man vill kunna kontrollera syntaxen i sina mallar genom kompilering kan skapa en.cpp-fil som bara inkluderar headerfilen och kompilera den, man kan även temporärt döpa om headerfilen till ett namn med filtypen.cpp och kompilera. Obs dock att det bara är begränsad syntaxkontroll kompilatorn kan göra på oinstansierade mallar - den vet ju inte för vilka typer mallen kommer att instansieras. Bild 168 24

Icke-typ-mallparametrar Mallparametrar behöver inte vara typer, de kan vara värden - argumentvärdet vid instansiering måste då vara ett konstant uttryck. template<class TYP, int size> class Buffer{ int count; TYP arr[size]; Buffer():count(0){ ; int main(){ Buffer<char, 256> cbuf; Buffer<string, 10> sbuf; Obs att detta värde kompileras in, cbuf och sbuf är objekt av två helt olika klasser med konstanta storlekar på sina arrayer. Bild 169 Mallspecialiseringar är olika typer Mallspecialiseringar är helt olika typer som inte har något med varandra att göra. Exempel: class Klass{ TYP data; Klass(TYP d):data(d){ void visa() const { Klass<string> Klass<> Klass<int> cout << data << endl; ks How many ki roads must a man walk 42 down? ; int main(){ Klass<string> ks("how many roads must a man walk down?"); Klass<int> ki(42); ks.visa(); ki.visa(); Ovanstående går bra. Men om vi vill ha en samling av sådana objekt (med olika typer på data) för att gå igenom dem och anropa visa() för varje objekt så går det inte eftersom objekten är av olika typer. Bild 170 25

Basklasser till klassmallar Om man vill att objekt av klasser som genereras ur en mall skall kunna hanteras som hörande till samma typ (t.ex. pekas ut från samma datastruktur) måste de utrustas med en gemensam basklass (som inte är en mall): class Klass_base{ virtual void visa() const = 0; ; class Klass:public Klass_base{ TYP data; Klass(TYP d):data(d){ void visa() const { cout << data << endl; ; int main(){ Klass_base *arr[2]; arr[0]=new Klass<string>("How many roads was it?"); arr[1]=new Klass<int>(42); for(int i=0; i<2; i++) arr[i]->visa(); Den gemensamma typen är Klass_base. Obs att de funktioner som skall kunna anropas utan vetskap om typen måste virtual-deklareras i basklassen. Obs att Klass_base inte kan innehålla något som är beroende av mallargumentet (typen). Obs att objekten ny är polymorfa och bör hanteras via pekare (motsv.) Bild 171 Exempel ur inluppen I inluppen kommer man att vilja representera kopplingar till tillämpningens okända objekt av okänd typ och till en okänd medlemsfunktion i ett objektet. Detta görs med en pekare till objektet och en medlemspekare till medlemsfunktionen, men eftersom objektets typ är okänt måste det göras i en mall. Men sedan vill man kunna ha samlingar av sådana kopplingar - lösningen: en basklass: class Action_base{ virtual void perform() = 0; ; class Action : public Action_base { TYP *obj; void (TYP::*funk)(); Action(TYP *o, void(typ::*f)()):obj(o), funk(f){ void perform(){ (obj->*funk)(); ; Bild 172 26

Exempel ur inluppen, forts. Där man vill ha ett godtyckligt Action-objekt kan man nu ha en Action_base *: class Button : public Component{ string caption; Action_base *action; Button(, string cap, Action_base *a):, caption(cap), action(a){ bool handle_event(event eve) const { if (eve.what == terminal::return) { action->perform(); return true; else return false; Tillämpningen kan då skapa sina knappar med en koppling till en medlemsfunktion i sina objekt enligt följande exempel: new Button(, new Action<Value>(&v, &Value::oka)); (något förändrat vid användning av referensräknande smarta pekare ) Bild 173 Hjälpfunktioner för implicit instansiering av klassmallar Instansiering av funktionsmallar är implicit - kompilatorn gissar sig till mallargumenten ur funktionsanropet ( argument deduktion ). Instansiering av klassmallar är explicit - mallargumentet måste anges efter klassnamnet: new Button(, new Action<Value>(&v, &Value::oka)); För att underlätta skapande av objekt av mallklasser brukar biblioteksskapare tillhandahålla funktionsmallar för skapande av objekten - dessa funktionsmallar blir då bekvämare att använda: Action<TYP> *mk_action(typ *obj, void (TYP::*funk)()){ return new Action<TYP>(obj, funk); Tillämpningen kan nu skapa sina Action<>-objekt lite bekvämare: new Button(, mk_action(&v, &Value::oka)); Bild 174 27

Ett exempel från standardbiblioteket: pair<> Mallen för en struct som omsluter två värden (något förenklad). Tillgänglig genom #include <utility>, ligger i namespace std template <class T1, class T2> struct pair { typedef T1 first_type; typedef T1 second_type; T1 first; T2 second; pair() : first(t1()), second(t2()) { pair(const T1& a, const T2& b):first(a),second(b){ ; template <class T1, class T2> inline bool operator==(const pair<t1,t2>& x, const pair<t1,t2>& y){ template <class T1, class T2> inline bool operator<(const pair<t1, T2>& x, const pair<t1, T2>& y){ return x.first < y.first (!(y.first<x.first)&& x.second < y.second); o.s.v. Hjälpfunktion för implicit instansiering : template <class T1, class T2> inline pair<t1,t2> make_pair(const T1& x,const T2& y){ return pair<t1,t2>(x, y); Obs hur medlemmarna ges default-värden! Bild 175 Ett exempel från standardbiblioteket: pair<>, forts. pair<> skulle t.ex. kunna användas i inluppen om man ville ha en typ för skärmkoordinater (d.v.s. x- och y-koordinaten förpackade i en liten struct): #include <utility> namespace inlupp{ typedef pair<int, int> Koord; struct Rectangle{ bool in_my_area(const Koord& point) const throw() { return.. ; ; class.{ void funk(const Rectangle& rect) { Event eve=term.getnextevent(); if (rect.in_my_area(make_pair(eve.wherex, eve.wherey))) eller if (rect.in_my_area(koord(eve.wherex, eve.wherey))) Bild 176 28

Argumentdeduktion för funktionsmallar Vid implicit instansiering av funktionsmallar försöker kompilatorn deducera mallargumenten (typerna) ur typer för aktuella argumentvärden i funktionsanropet. Funktionens argument behöver inte vara av de typer som utgör mallargument, det räcker att mallargumenten framgår av funktionens argument. Exempel: template<class TYP> void funk(typ *pek){ TYP temp = *pek; ; int *ipek; funk(ipek); Funktionsargumentet är en int * så mallargumentet är int template<class TYP, int siz> void funk(typ (&arr)[siz]){ for(int i; i<siz; i++) cout << arr[i] << endl; ; char buff[128]; funk(buff); Funktionsargumentet är en char[128] så mallargumentet TYP är char och mallargumentet siz är 128 Bild 177 Explicit instansiering av funktionsmallar Ibland framgår inte mallargumenten ur funktionsargument. Detta inträffar främst då mallargumentet (typen) används som returtypen för funktionen, men inte som funktionsargumenttyp (returtypen deltar inte i deduktionen eftersom det inte alltid framgår av anropet vad den skall vara). I så fall måste mallargument anges explicit vid anropet: template <class RET, class ARG> RET funk(const ARG& param) { RET x; return x; string str; str=funk<string, int>(13); De mallargument som kan deduceras behöver inte anges om de står sist i listan: str=funk<string>(13); Bild 178 29

(Nästan) Ingen konvertering vid argumentdeduktion När en funktionsmall skall instansieras kollar kompilatorn om det redan finns en instans av funktionen med samma argumenttyper. Vid denna kontroll används dock inte de vanliga typkonverteringsmekanismerna. template<class TYP> TYP add(typ x, TYP y){ return x + y; Triviala konverteringar som array till pekare, funktion till funktionspekare, TYP till const TYP används dock. int i1, i2, i3; short sh1, sh2, sh3; long lo1, lo2, lo3; i3 = add(i1, i2); sh3 = add(sh1, sh2); lo3 = add(lo1, lo2); i1 = add(25, i3); // add(int, int) genereras // add(short, short) genereras // add(long, long) genereras // finns redan, används Bild 179 Överlagrade funktionsmallar Funktionsmallar kan överlagras med andra funktionsmallar och med vanliga funktioner. Det är den mest specialiserade funktionen kommer att anropas (efter eventuell instansiering). void funk(typ x){ cout << funk<typ> << endl; Vi återkommer till specialiserade mallar senare void funk(typ *x){ cout << funk<typ *> << endl; void funk(int x){ cout << funk<int> << endl; int main(){ int i=15; int *ipek=&i; string str( Jozef ); funk(i); // Skriver funk<int> funk(ipek); // Skriver funk<typ *> funk( Jozef ); // Skriver funk<typ *> funk(str); // Skriver funk<typ> Bild 180 30

Exempel från inluppen Man kan tänka sig att Action<>-objekt även borde kunna knytas till fria funktioner och till medlemsfunktioner som kan ta t.ex. en knapps textsträng som argument. Man kan inte ha flera klasser med samma namn, så klassmallar kan inte överlagras, vi får hitta på nya namn för sådana klasser: class Action_arg : public Action_base { std::string arg; TYP *obj; void (TYP::*funk)(std::string); Action_arg(std::string a, TYP *o, void(typ::*f)(std::string)): arg(a), obj(o), funk(f){ void perform(){ (obj->*funk)(arg); ; class Action_free : public Action_base { // Inte ens en mall void (*funk)(); Action_free(void(*f)()):funk(f){ void perform(){ funk(); ; Bild 181 Exempel från inluppen, forts. Vi kan dock underlätta för tillämpningsprogrammerare genom att skapa överlagrade funktionsmallar för hjälpfunktioner: template <class T> Action_arg<T> *mk_action(string a, T *o, void (T::*f)(string)){ return new Action_arg<T>(a, o, f); Action_free *mk_action(void (*funk)()){ return new Action_free(funk); Tillämpningsprogrammeraren kan nu skapa sina Action<>-objekt på samma sätt : new Button(, mk_action(&v, &Value::oka)); new Button(, mk_action( Minska, &v, &Value::minska)); new Button(, mk_action(quit)); Bild 182 31

Statiska medlemmar i klassmallar Varje mallspecialisering får sin egen kopia av statiska medlemmar: class Counted{ static int count; TYP data; Counted(TYP s):data(s){ count++; Counted(const Counted& other):data(other.data){ count++; ~Counted(){ count--; TYP get_data() const { return data; static int get_count(); ; int Counted<TYP>::count=0; int Counted<TYP>::get_count(){ return count; Klasser genererade ur denna mall (t.ex. Counted<int>, Counted<string>) har inget gemensamt, varje klass kommer att ha en egen count och get_count(). Bild 183 Statiska medlemmar över mallgränser Vill man att klasser genererade ur en mall skall ha en gemensam typ och/eller medlemmar måste de ha en gemensam basklass: class Counted{ static int totcount; Counted(){ totcount++; Counted(const Counted& other)( totcount++; virtual ~Counted(){ totcount--; static int get_totcount(); ; int Counted::totcount=0; int Counted::get_totcount() { return totcount; // fortsättning på nästa bild Bild 184 32

Statiska medlemmar över mallgränser - forts. class DataCounted : public Counted{ static int count; TYP data; DataCounted(TYP d):data(d){count++;//default-ktor anropas DataCounted(const DataCounted<TYP>& other): Counted(other), data(other.data){count++; ~DataCounted(){count--; // Counted-dtor anropas automatiskt TYP get_data() const { return data; static int get_count(); ; int DataCounted<TYP>::count=0; int DataCounted<TYP>::get_count(){ return count; Bild 185 Statiska medlemmar över mallgränser - användning. int main(){ DataCounted<int> di1(13), di2(77); DataCounted<string> ds1( Peter ), ds2( Ulla ); cout << Totalt << Counted::get_totcount(); cout << varav int << DataCounted<int>::get_count(); cout << och str << DataCounted<string>::get_count(); Bild 186 33

friend-deklarationer i klassmallar Med en friend-deklaration i en klassmall kan man mena tre olika saker: 1. vännen är en viss specifik funktion eller klass 2. vännen är en instans av en funktions- eller klassmall instansierad med samma typ (-er) som denna klass I detta fall måste deklarationer av dessa funktions- eller klassmallar finnas inom deklarationsvidden, före klassmallen som deklarerar dem som vänner Speciell syntax (se nästa bild). 3. vännen är en godtycklig instans av en viss funktions- eller klassmall. I detta fall blir friend-deklarationen i klassen en mall i sig: class Klass{ template <class TYP2> friend void funk(const TYP2&); Bild 187 friend-deklarationer i klassmallar, exempel Exempel på fall 2 från föregående bild: säg att vi vill lösa problemet med den specialiserade swap-funktionens åtkomst till Vectors privata data genom att deklarera swap-funktionen som vän: void swap(typ& x, TYP& y); class Vector; void swap(vector<typ>& x, Vector<TYP>& y); class Vector{ friend void swap<typ>(vector<typ>&, Vector<TYP>&); ; void swap(vector<typ>& x, Vector<TYP>& y){ int s=x.siz, c=x.cap; TYP *a = x.arr; x.siz=y.siz; x.cap=y.cap; x.arr=y.arr; y.siz=s; y.cap=c; y.arr=a; Bild 188 34

Medlemsmallar I standard-c++ kan man göra medlemmar i en klass (eller klassmall) till mall. Syntaktiskt exempel: class Klass{ string namn; Klass(string n):namn(n){ ; void skriv(const TYP& meddel) const { cout << namn << : << meddel; int main(){ Klass teacher( Jozef ); const string tidsenh( minuter ); teacher.skriv( Vi tar paus på ); teacher.skriv(17); teacher.skriv(tidsenh); Bild 189 Medlemsmallar, mer realistiskt exempel Säg att vi i vår Vector<>-mall vill ha en konstruktor som initierar vektorn med värden som tas från vilken annan typ av behållare som helst, bara elementen är av rätt typ och kan pekas ut av pekare (eller något som beter sig som pekare): class Vector{ int siz, cap; TYP *arr; template <class PEKTYP> // Deklaration Vector(PEKTYP pek1, PEKTYP pek2); ; // Definition template <class PEKTYP> Vector<TYP>::Vector(PEKTYP pek1, PEKTYP pek2): siz(0), cap(10), arr(new TYP[cap]) { while (pek1!= pek2) push_back(*pek1++); Bild 190 35

Medlemsmallar, forts. Exempel på användning: #include <list> #include Vector.h int main(){ list<int> li; int arr[] = {13, 7, 78, 53, 92; const int arrsize = sizeof(arr) / sizeof(int); for(int i=0; i<arrsize; i++) li.push_back(arr[i]); Vector<int> v1(li.begin(), li.end()); // Vector<int>::Vector(list<int>::iterator,list<int>::iterator) Vector<int> v2(&arr[0], &arr[arrsize]); // Vector<int>::Vector(int *, int *) Bild 191 Konvertering mellan mallspecialiseringar I vissa speciella situationer vill man att objekt av olika klasser genererade ur samma mall skall kunna konverteras till varandra. Detta inträffar t.ex. för mallar för referensräknande pekarklasser som skall användas för att peka ut objekt ur en klasshierarki: ett pekarobjekt för basklassen skall kunna tilldelas från ett pekarobjekt för subklassen enligt de vanliga reglerna för basklass-/subklass-pekare. För att möjliggöra detta får man i mallen för pekarklassen skapa en mall för konverteringsoperatorer. Först en repetition av konverteringsoperatorer: class Alfa{ ; class Beta{ operator Alfa() { // Detta är en konverteringsoperator som anger // hur man gör ett Alfa-objekt ur Beta-objektet ; Bild 192 36

Konvertering mellan mallspecialiseringar, forts. Om det istället är fråga om konverteringar mellan två klasser genererade ur samma mall för man göra en mall för konvertering till specialisering för en annan typ: class Alfa{ template <class ANNANTYP> operator Alfa<ANNANTYP>(); ; template <class ANNANTYP> Alfa<TYP>::operator Alfa<ANNANTYP>(){ return ett temporärt Alfa<ANNANTYP>-objekt konstruerat ur detta Alfa<TYP>-objekt Bild 193 Mall för pekarklass för polymorfa objekt class null_pointer; // Undantagsklass, visas senare template <class T> class Ptr{ T *ptr; int *count; template<class U> friend class Ptr; Ptr(T *p, int *c); // Privat konstruktor, används vid konvertering Ptr(T *p=0) throw(std::bad_alloc); Ptr(const Ptr& other) throw(); ~Ptr() throw(); const Ptr& operator=(const Ptr& other) throw(); T& operator*() throw(null_pointer); const T& operator*() const throw(null_pointer); T* operator->() throw(null_pointer); const T* operator->() const throw(null_pointer); bool operator==(const Ptr& other) const throw(); bool operator!=(const Ptr& other) const throw(); operator T*() const throw(); // Konvertering till vanlig pekare template <class OTHERTYPE> // Konvertering till Ptr för annan operator Ptr<OTHERTYPE>(); // typ (basklasstyp) ; Bild 194 37