Föreläsning 5-7 Operatoröverlagring

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

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

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

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

Tentamen *:85/2I4123 C

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

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

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

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

Lektionsuppgifter. TDDI14 Objektorienterad programmering. Lektionsplanering Lektion Lektion Lektion

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

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

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

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

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

Synlighet. Namespace Scope-operatorn Klasser Vänner

TDIU01 - Programmering i C++, grundkurs

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

Tillämpad programmering

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

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

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

TDDC76 - Programmering och Datastrukturer

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

TDIU01 Programmering i C++

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

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

SP:PROG3 HT12 Tenta

C++-programmets beståndsdelar

Programmering i C++ EDA623 Typer. EDA623 (Föreläsning 4) HT / 33

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

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

TDIU01 - Programmering i C++, grundkurs

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

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

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

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

Tommy Färnqvist, IDA, Linköpings universitet

Pekare och arrayer. Indexering och avreferering

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

Innehåll. Pekare Exempel

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

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

2D1387, Programsystemkonstruktion med C++ Johnny Bigert, Kursens hemsida:

TDDC76 - Programmering och Datastrukturer

Funktionspekare, inledning: funktionsanropsmekanismen. Anrop via pekare

Datatyper och kontrollstrukturer. Skansholm: Kapitel 2) De åtta primitiva typerna. Typ Innehåll Defaultvärde Storlek

Kapitel 1. C++-programmets beståndsdelar. C++-programmets beståndsdelar. Kapitel 1 grunderna i C++

TDIU01 - Programmering i C++, grundkurs

Dynamisk bindning och polymorfism

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

Innehåll. Pekare Exempel

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

Tentamen EDAF30 Programmering i C++

Algoritmbiblioteket (STL) Designstrategi Generiska algoritmer som fungerar på godtyckliga samlingsdatatyper, vilka har iteratorer.

Föreläsning 2 Objektorienterad programmering DD1332. Typomvandling

Föreläsning 5 (6) Metoder. Metoder Deklarera. Metoder. Parametrar Returvärden Överlagring Konstruktorer Statiska metoder tostring() metoden javadoc

Föreläsning 4 Innehåll. Abstrakta datatypen lista. Implementering av listor. Abstrakt datatypen lista. Abstrakt datatyp

Programsystemkonstruktion med C++

Kapitel 4 - Mallar. Kapitel 4. Introduktion till mallar STL. 2D1387 Programsystemkonstruktion med C++ 1

Standard Template Library STL. Behållarklasser

Kapitel 4. Funktionsmallar. Mallar. Introduktion till mallar STL

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

allokeras på stacken dynamiskt new delete

Innehåll. 1 Funktionsmallar. 2 Pekare och konstanter. 3 Typomvandlingar. struct Name { string s; //... };

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

Minneshantering. Minneshantering. Minneshantering. Undvik pekare

Kapitel 6 - Undantag

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

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

Tentamen EDAF30 Programmering i C++

Java, klasser, objekt (Skansholm: Kapitel 2)

TDDI14 Objektorienterad programmering

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

Kapitel 4. Funktionsmallar. Mallar. Introduktion till mallar STL

Föreläsning 5-6 Innehåll

2D1387, Programsystemkonstruktion med C++ Johnny Bigert, Kursassistent: Mårten Björkman,

int (*fp) (char, char*) //pekare till funktion som tar //argumenten (char, char*) och //returnerar int

Innehåll. Pekare Syntax

TDDC76 - Programmering och Datastrukturer

Det finns många flaggor till g++,

Typkonvertering. Java versus C

Innehåll. Typomvandlingar (casting) Implicita Typomvandlingar. Typomvandlingar (casting) Implicita Typomvandlingar

Skapa, kopiera och destruera klassobjekt

Föreläsning 3-4 Innehåll. Diskutera. Metod. Programexempel med metod

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

Funktionens deklaration

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

Minnestilldelning (allokering) och frigörande (avallokering) av minne

C++ Objektorientering - Klasser. Eric Elfving

Föreläsning 3-4 Innehåll

Inkapsling tumregler. Åtkomstmodifikatorer, instantiering, referenser, identitet och ekvivalens, samt klassvariabler. public och private

Tentamen *:58/ID100V Programmering i C Exempel 3

Tentamen i Objektorienterad Programmering 5p, Au, D, Fri, Pr,

Uppgiften är att beskriva en kvadrat i ett Java program. En första version av programmet skulle kunna se ut så här:

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

OOP Objekt-orienterad programmering

public och private Obs: private inte skyddar mot access från andra objekt i samma klass.

Generell (template) programmering. Effektiv C++ Slutliga tips Genomgång av gammal tenta. Daniel Aarno Allt som fungerar som x ÄR x

3. Minneshantering - (copy)konstruktorn, destruktorn och tilldelningsoperatorn

Transkript:

*:85/ID200V C++ HT07 Föreläsning 5-7 Operatoröverlagring Operatoröverlagring Följande operatorer kan inte överlagras: ::..*?: sizeof typeid Alla övriga operatorer kan överlagras. Följande operatorer måste vid överlagring göras till medlemsfunktioner: tilldelningsoperatorer = += -= *= osv indexeringsoperatorn [] anropsoperatorn () medlemsåtkomst via pekare -> Övriga överlagringsbara operatorer kan göras till medlemsfunktioner eller till funktioner utanför klassen Bild 78 1

Operatoröverlagring Exempel för OH-bilderna: antag följande klass för representation av klockslag eller tidsåtgång: class Klocka{ Klocka(int m=0) : min(m){ // Default-ktor Klocka(int h, int m) : min(h*60+m){ int timme() const { return min / 60; int minuter() const { return min % 60; int totmin() const { return min; private: int min; ; Exempel som löses på föreläsningen: i en del tillämpningar (speciellt sådana som har att göra med kryptering) behöver man hantera heltal som är mycket större än vad som kan representeras med inbyggda heltalstyper. Skapa en klass vars objekt beter sig som heltal utan förtecken (unsigned) av obegränsad storlek. Vi begränsar oss till additionsoperatorer för dessa heltal. Bild 79 Operatoröverlagring forts. Vi kan definiera t.ex. följande funktioner: const Klocka operator+(const Klocka& k1, const Klocka& k2){ return Klocka(k1.totmin()+k2.totmin()); const Klocka operator+(const Klocka& k, int i){ return Klocka(k.totmin()+i); inline const Klocka operator+(int i, const Klocka& k){ return k+i; // Anropar bara ovanstående operator+ int operator-(const Klocka& k1, const Klocka& k2){ return k1.totmin()-k2.totmin(); Obs att det är biblioteksskaparens uppgift att se till de vanliga reglerna för operatorerna gäller, t.ex. att additionen är kommutativ (kan man göra kl + 3 så borde man kunna göra 3 + kl) Att returtypen för de operatorer som returnerar Klocka-objekt deklareras som const Klocka beror på att man vill se det returnerade objektet som ett värde, inte som ett lvalue (tilldelningsbart objekt). Bild 80 2

Operatoröverlagring - medlem eller fri funktion Operatorer som görs som medlemsfunktioner har som sin vänstra operand objektet självt och deklareras med en operand mindre. De har tillgång till objektets privata data. Operatorer vilkas vänstra operand är av en annan typ kan inte göras så. class Klocka { int min; int totmin() const{ return tot_minuter; const Klocka operator+(const Klocka& k) const; ; const Klocka Klocka::operator+(const Klocka& k) const { return Klocka(min + k.min); Operatorer som görs utanför klassen deklareras med alla sina operander som argument. De har inte tillgång till objektets privata data (kan lösas med frienddeklarationer): const Klocka operator+(int min, const Klocka& k){ return Klocka(min + k.totmin()); Bild 81 Relationsoperatorer Det vore ju bra om två klockslag kunde jämföras - detta kräver definition av jämförelseoperatorer med Klocka-operander. Obs att ingen operator genereras automatiskt (utom tilldelningsoperatorn): att man definierat operator== betyder inte att operator!= är definierad (se dock nästa bild). bool operator==(const Klocka& k1, const Klocka& k2){ return k1.timme()==k2.timme()&&k1.minuter()==k2.minuter(); inline bool operator!=(const Klocka& k1, const Klocka& k2){ return!(k1 == k2); Obs att det är en god vana att använda redan definierade operatorer vid definition av en ny operator (om det går). På detta sätt försäkrar man sig om att operatorernas semantik är konsekvent och underlättar ev. modifieringar. Vidare: bool operator<(const Klocka& k1, const Klocka& k2){ return k1.timme()<k2.timme() k1.timme() == k2.timme() && k1.minuter()<k2.minuter(); bool operator<=(const Klocka& k1, const Klocka& k2){ return k1 < k2 k1 == k2; Bild 82 Och så vidare 3

To the rescue - mallar från std I standardbiblioteket finns mallar för relationsoperatorer definierade m.h.a. operator== och operator< Om dessa definitioner duger behöver man alltså bara definiera dessa två operatorer och inkludera mallarna. Tillgängliga genom #include <utility>, finns i namnrymden std::rel_ops template <class T> inline bool operator!=(const T& x, const T& y) { return!(x == y); template <class T> inline bool operator>(const T& x, const T& y) { return y < x; template <class T> inline bool operator<=(const T& x, const T& y) { return!(y < x); template <class T> inline bool operator>=(const T& x, const T& y) { return!(x < y); Bild 83 Konverteringsoperationer Vad skulle kompilatorn säga om följande sats (med endast de hittills visade operatorerna och om kl är ett objekt av typen Klocka): if (kl < 50) Jo, det går hur bra (eller dåligt, beroende på vad man vill) som helst. Kompilatorn försöker hitta en operator<(klocka&, int), hittar ingen sådan men hittar en operator<(const Klocka&, const Klocka&) och dessutom en Klocka::Klocka(int), d.v.s. den kan konvertera en int till en Klocka (skapa temporärt objekt) och utföra operationen. Vill man förbjuda detta kan man deklarera Klocka konstruktorn som explicit. Går det då att göra tvärtom (använda Klocka där en int förväntas), t.ex.: int m = kl; Nej, kompilatorn vet inte hur man konverterar Klocka till int. Om detta är önskvärt (mycket tveksamt, se det bara som syntaktiskt exempel) kan man definiera en konverteringsoperator till int i klassen Klocka (se nästa bild). Bild 84 4

Konverteringsoperatorer En konstruktor kan användas för att konvertera från en annan typ till denna klass (explicit eller implicit), t.ex. från int till Klocka Om man vill ange hur ett objekt av klassen kan konverteras (explicit eller implicit) till en annan typ kan man utrusta klassen med en konverteringsoperator, vars namn är typen man skall konvertera till och som deklareras utan returtyp: class Klocka{ int min; explicit Klocka(int m=0):min(m){ operator int() { return min; ; Detta gör att ett Klocka-objekt accepteras där en int förväntas. Explicit konvertering till int av Klocka kl görs med int(kl). Explicit konvertering av int i till Klocka kan fortfarande göras med Klocka(i). Implicit konvertering av int till Klocka är nu förbjuden (kompileringsfel). Obs att man inte kan ha båda: om konstruktorn som tar en int inte vore explicit får man tvetydighet vid behov till implicit konvertering, båda konverteringarna duger lika bra! Bild 85 Uppdateringsoperatorer För typer där både tilldelningsoperatorn och vanliga binära operatorer (t.ex. binär +, binär - o.s.v.) är definierade förväntar sig användare att även uppdateringsoperatorer (+=, -= o.s.v.) kan användas. Dessa borde alltså också definieras. Vår klass Klocka har ju den automatiskt genererade tilldelningsoperatorn och så har vi definierat operatorerna operator+ med en annan Klocka och med int samt operator- med en annan Klocka. Alltså borde vi definiera motsvarande += resp. -= Sådana operatorer måste göras som medlemsfunktioner. Eftersom de ändrar det egna objektet kan de inte deklareras som const. Eftersom de skall returnera det tilldelade värdet (de borde ju bete sig som andra likadana operatorer) så brukar man returnera en referens till objektet självt, men som en const-referens så att returen inte skall vara tilldelningsbar. För att återanvända redan implementerade operationer brukar man definiera uppdateringsoperatorn först och sedan använda den i definitionen av motsvarande binära operator (se nästa bild). Bild 86 5

Exempel på uppdateringsoperatorer class Klocka{ int min; const Klocka& operator+=(const Klocka&); const Klocka operator+(const Klocka&) const; ; const Klocka& Klocka::operator+=(const Klocka& other){ min += other.min; return *this; const Klocka Klocka::operator+(const Klocka& other) const{ Klocka resultat = *this; resultat += other; return resultat; Bild 87 Konstigheter med ++ och -- För typer där det finns tilldelning samt addition och subtraktion förväntar sig användare att det även finns ++ och --. Inkrementeringsoperatorn ++ och dekrementeringsoperatorn -- finns ju i två varianter: prefix och postfix. I dessa två varianter har de olika semantik men samma signatur. På något sätt måste man ange i deklarationen av dem vilken som är prefix och vilken som är postfix. Detta görs genom att postfix-operatorerna utrustas i deklarationen (och definitionen) med ett extra dummy int-argument som varken används eller ens namnges (!). Prefix-operatorerna borde returnera objektet självt som en const-referens. Postfix-operatorerna kan inte göra det - de skall ju returnera värdet innan förändringen. De bör alltså skapa ett nytt objekt och returnera det (alltså objektet, inte en referens). class Klocka{ int min; const Klocka& operator++() { // Prefix return *this += 1; const Klocka operator++(int) { Klocka tmp(*this); *this += 1; return tmp; // Postfix Bild 88 6

Överlagring av operator() Om man deklarerar anropsoperatorn operator() i en klass så innebär det bara att man kan anropa denna operator genom att skriva parenteserna med eventuella argumentvärden efter ett objektnamn. Denna operator är den enda där antalet argument inte är bestämt av språkdefinitionen. class Exempel { string str; Exempel(string s):str(s) { void operator()(int x, string mer){ cout << str << mer << x << endl; ; int main(){ Exempel ex( Karl ); ex(16, Gustav ); // Anropar operator() Bild 89 operator(), användning - funktionsobjekt Den viktigaste användningen av överlagrad anropsoperator är för skapande av s.k. funktionsobjekt (funktorer), d.v.s. objekt som omsluter en funktion och kan användas som en funktion för skickas till andra funktioner som argument (istället för funktionspekare). Exempel: i standardbibliotekets headerfil <algorithms> finns bl.a. följande funktionsmall motsvarande våra doforall-funktioner från moment *:58 (obs! förenklad här!): template <class Iter, class Oper> void for_each(iter first, Iter last, Oper doit) { for ( ; first!= last; ++first) doit(*first); Exempel på användning: void show_one(int value) { cout << value; int main(){ vector<int> vec; // Fyll vec med värden på nå t sätt for_each(vec.begin(), vec.end(), show_one); Endast orinentering, återkommer vid genomgång av STL Bild 90 7

Funktionsobjekt, exempel, forts. Antag att vi ofta behöver öka alla element i vektorn med samma värde, men detta värde är olika vid olika tillfällen. En funktion som skulle kunna skickas till for_each för att göra detta behöver ta två argument: en referens till elementet som ska ökas och värdet som ska adderas men for_each anropar funktionen med endast elementet Genom att istället för en funktionspekare skicka ett objekt av en struct som omsluter funktionen kan detta objekt även innehålla värdet som funktionen ska använda - vi skickar en funktion med en liten omgivning: struct Add{ int talet; Add(int tal):talet(tal){ void operator()(int& elem){ elem += talet; ; int main(){ vector<int> vec; // Fyll vec med värden for_each(vec.begin(), vec.end(), Add(5)); I for_each-anropet skapas ett temporärt objekt av Add och värdet 5 lagras i objektet, detta objekt används av for_each som en funktion. Bild 91 operator(), användning - indexering med flera index Eftersom operator() är den enda där antalet argument inte är bestämt av språkdefinitionen används den ofta även där indexeringsoperatorn egentligen borde användas, men där man vill ha flera index. Exempel: class Matrix { int rows, cols; int *data; Matrix(int r, int c): rows(r), cols(c), data(new int[rows * cols]){ ; int& operator()(int r, int c){ return data[r * cols + c]; int main(){ Matrix mat(10, 30); mat(3, 5) = 177; Bild 92 8

Överlagring av operator[] Indexeringsoperatorn operator[] brukar överlagras i klasser som fungerar som en datasamling, den brukar ge åtkomst till ett element i datasamlingen identifierat med ett värde (ofta ett heltalsindex, men det är inte nödvändigt). Den ska ha ett argument. I icke-const-objekt borde den returnera ett lvalue (alltså en referens) så att den ska kunna användas både för avläsning av elementvärdet och för tilldelning till elementet. Brukar även överlagras med en const-variant som returnerar ett värde. class Text{ int siz; char *cptr; char& operator[](int index){ return cptr[index]; char operator[] (int index) const { return cptr[index]; ; Bild 93 Exempel: matris med vanliga indexeringsoperatorer Det vanliga sättet att indexera i en matris i C-liknande språk är med dubbel indexering: mat[3][5] = 177; Här är det alltså resultatet av den första indexeringen som indexeras i sin tur. För att åstadkomma detta får vi införa en extra klass eller struct som returneras som resultat av den första indexeringen: class Matrix { int rows, cols; int *data; struct Col{ Matrix& mx; int r; Col(Matrix& m, int rr):mx(m), r(rr){ int& operator[](int c) { return mx.data[r*mx.cols+c]; ; Här har vi ett exempel på en nästlad struct. Obs! skillnad mot Javas inre klasser! Objekt av en nästlad struct eller klass i C++ ligger inte inom sitt omgivande objekt. Matrix(int r, int c): rows(r), cols(c), data(new int[rows * cols]){ Col operator[](int r){ return Col(*this, r); ; Bild 94 9

operator[], skilja på avläsning och tilldelning Ibland vill man använda använda operator[] för åtkomst, men måste göra operationerna avläsning resp. tilldelning på olika sätt. Man kan då från operator[] returnera ett temporärt objekt av en privat inre klass med en konverterings- och en tilldelningsoperator. Exempel: en klass som beter sig som en bitvektor med indexering till ensklida bitar(!) class Bitvector{ unsigned long bits; struct Bref{ unsigned long& b; int pos; Bref(unsigned long& bb, int p):b(bb), pos(p){ operator bool() const { return b & (1ul << pos); bool operator=(const bool v) { if (v) b =1ul<<pos; else b&=~(1ul<<pos); ; Bitvector():bits(0){ Bref operator[](int pos){return Bref(bits, pos); ; Bild 95 Överlagring av operator-> operator-> har lite speciellt beteende: den skall returnera en pekare eller en referens till ett objekt av en klass som också överlagrar denna operator. Om returtypen är en pekare så avrefereras pekaren och åtkomst görs till den angivna medlemmen i det utpekade objektet (som om klassen som överlagrar operatorn själv var en pekare). Om returtypen är en referens till ett objekt av en klass som också överlagrar operator-> så anropas denna operator och mekanismen tillämpas rekursivt tills en pekare returneras, varefter pekaren avrefereras och medlemsåtkomsten görs. Överlagrad operator-> måste vara implementerad som medlem i klassen. Med hjälp av överlagring av operator-> (ofta i kombination med den unära operator*) kan man skapa klasser som beter sig som pekare men kan utföra kontroller, minneshantering osv (s.k. smarta pekare). Behållarklassernas iteratorer utgör ett exempel på sådana klasser. Bild 96 10

Pekarklasser Vid byggande av bibliotek av klasser där det är meningen att tillämpningar skall skapa objekten dynamiskt kan man underlätta hanteringen av objektavallokering genom att även skapa klasser vilkas objekt beter sig som pekare men som kontrollerar de utpekade objektens liv genom att när det är dags avallokera dem. Vad som är lämplig tidpunkt för avallokering varierar med situationen varför det har varit svårt att skapa bibliotekskomponenter för detta. På nästa bild finns det en skiss på en sådan klass för hantering av objekt av en klass Person. Klassen Ptr låtsas vara en pekare, den skulle kunna användas i en tillämpning enligt följande: void funk(){ Ptr p=new Person( Jozef ); cout << p->getname(); // åtkomst till medlem i utpekat objekt Person annan(*p); // avreferering ger det utpekade objektet // funktionen slut, p städas bort och i sin destruktor tar bort utpekat objekt Bild 97 Exempel: klass som simulerar pekare class Person{; class Ptr{ Person *ptr; // info om utpekade objektets status Ptr(Person *p): ptr(p){ /* uppdatera status */ Ptr(Ptr& other):ptr(other.ptr){/* uppdatera status */ Ptr& operator=(const Ptr& other){ /* updatera status */ ~PersPtr(){ if (/* skall bort */) delete ptr; ; Person& operator*() { return *ptr; Person* operator->() { return ptr; bool operator==(const Ptr& other) const; bool operator!=(const Ptr& other) const; Bild 98 11

Referensräkning En lösning som duger i många situationer är pekarklasser som håller reda på hur många pekarobjekt som pekar ut samma objekt och som städer bort objektet när det sista pekarobjektet försvinner: class Person; class Ptr { Person *ptr; int *count; // Räknare som pekarobjekten till samma obj. delar på Ptr(Person *pek=0); Ptr(const Ptr& other); ~Ptr(); Ptr& operator=(const Ptr& other); Person& operator*() const { return *ptr; Person* operator->() const { return ptr; bool operator==(const Ptr& other) const; bool operator!=(const Ptr& other) const; ; Bild 99 Referensräkning - implementering av medlemsfunktioner Ptr::Ptr(Person *pek) : ptr(pek), count((pek)? new int(1) : 0){ Ptr::Ptr(const Ptr& other) : ptr(other.ptr), count(other.count){ if (ptr) (*count)++; Ptr::~Ptr(){ if (ptr && --*count == 0){ delete count; delete ptr; bool Ptr::operator==(const Ptr& other) const{ return ptr == other.ptr; bool Ptr::operator!=(const Ptr& other) const{ return ptr!= other.ptr; Bild 100 12

Referensräkning - implementering av tilldelning Ptr& Ptr::operator=(const Ptr& other){ if (ptr!= other.ptr){ if (ptr && --*count == 0){ delete count; delete ptr; ptr = other.ptr; count = other.count; if (ptr) ++*count; return *this; Användning av denna pekarklass förutsätter att användaren har disciplinen att inte använda vanliga pekare - vår Ptr kan ju inte veta hur många vanliga pekare som pekar ut de utpekade Person-objekten. Detta kan underlättas genom att vid objektskapande påtvinga användning av en funktion som returnerar ett Ptr-objekt. Bild 101 Person-klassen för referensräkning Genom att göra konstruktorn privat och skapa en publik statisk medlemsfunktion som skapar ett objekt dynamiskt och returnerar ett Ptr-objekt kan man se till att tillämpningar inte kan skapa objekt på annat sätt: class Person{ string namn; int nummer; Person(string na, int nu):namn(na), nummer(nu){ static Ptr create(string na, int nu); string get_namn() const { return namn; int get_nummer() const { return nummer; ; // Person Ptr Person::create(string na, int nu){ return Ptr(new Person(na, nu)); Objektskapande funktioner brukar kallas fabriksfunktioner (factory functions) Tekniken med privat konstruktor och en objektskapande statisk medlemsfunktion (eller friend-funktion) brukar kallas the named constructor idiom. Bild 102 13

Person-klassen för referensräkning, forts. Om man verkligen vill försäkra sig om att Person-objekten bara skapas på heapen och hanteras genom Ptr-objekt bör man även förbjuda kopiering av Person-objekt och adresstagning för dem: class Person{ // som tidigare Person(const Person& other); Person *operator&(); const Person *operator&() const; // som tidigare ; // Person Detta brukar göras genom att man deklarerar copy-konstruktorn och adresstagningsoperatorer (för icke-const-resp. const-objekt) som privata och utan definition (det senare dels för att definitionen är onödig, dels för att det skyddar mot att man använder dessa operationer själv i klassen Person - ger då länkningsfel). Eftersom detta kan behöva göras för många klasser brukar man definiera ett preprocessormacro för sådana deklarationer. Bild 103 Standardbibliotekets auto_ptr<> Standardbiblioteket tillhandahåller i <memory> mallen för en pekarklass kallad auto_ptr<>. Semantiken för denna klass är att ett auto_ptr<>-objekt äger det utpekade objektet och tar bort det i sin destruktor. Vid kopiering och tilldelning överförs ägarskapet till det nya auto_ptr<> och det gamla sätts att peka på NULL. Det är alltså endast det senast tilldelade eller konstruerade auto_ptr<> som pekar ut objektet. Detta gäller även vid värdeöverföring som argument eller vid värderetur från funktioner. auto_ptr<> kan inte användas i situationer där flera pekare kan tänkas peka ut samma objekt, t.ex. i behållare. auto_ptr<>s användningsområde är för situationer då dynamiska objekt skapas, används och skall städas bort i funktioner som kan avbrytas av undantag. Bild 104 14