Institutionen för datavetenskap Umeå Universitet F8: Typkonvertering i C++ Objektorienterad programmering för ingenjörer, VT04
Innehåll Implicit konvertering Konverteringskonstruktor Konverteringsoperator Explicit konvertering static_cast dynamic_cast const_cast reinterpret_cast Virtuella arv Felaktig arvsdesign down casting 04-05-25 2
Typkonvertering Implicit konvertering T.ex. double a = 6.0; double b = a / 4; 4 konverteras till double automatiskt int b = a / 4; Ger olika resultat Explicit konvertering T.ex. double d = a / (double)4; Hur hanteras då egen-definerade typer? T.ex. konvertering av klassen A till int <-- vad händer då? 04-05-25 3
Implicit konvertering Konverteringskonstruktor A::A(const B& rhs) A::A(const string& rhs) A::A(int rhs) A::A(char*) Konverteringsoperator A::operator B() const A::operator string() const A::operator int() const A::operator char*() const Notera att ingen returtyp är deklarerad men funktionen måste ändå returnera ett värde Kan vara källa till fel! Oönskad konvertering sker... kompilatorn vill för mycket 04-05-25 4
Implicit konvertering A a = 5.0; Konverteringsoperator från int är deklarerad Fel typ 5.0 Konvertering till int 5 Konvertering till egendef. typ A 5 Typ: A int double 04-05-25 5 5
Implicit konvertering A a = 5; Om operator=(int) ej är deklarerad körs konverteringskonstruktorn A::A(int) a = 5; Om operator=(int) ej finns körs konverteringskonstruktorn (const char*)a; Konverteringsoperatorn A::operator const char*() const körs b = (const char*)a; Konverteringskonstruktorn och konv-operator körs b = 5.0; Krångligt!? Om double-konvertering inte finns men int-konvertering finns konverteras 5.0 implicit till en int! 04-05-25 6
Implicit konvertering Det finns en förklaring... Konverteringskonstruktorn anropas när ett temporärt objekt måste skapas och konvertering krävs a = 5; ==> a = A(5) Konverteringsoperatorn anropas när konvertering krävs och ett temporärt objekt ej behöver skapas (const char*)a; vid b = (const char*)a; så skapas även ett temporärt objekt och konverteringskonstruktorn anropas också Om operator=(int) finns så behövs ingen konvertering göras 04-05-25 7
Exempel convop.cc 04-05-25 8
Explicit konverting 4 typer av konverteringar static_cast<t>() dynamic_cast<t>() const_cast<t>() reinterpret_cast<t>() Ger viss kontroll till hur typer konverteras 04-05-25 9
static_cast Konvertera uttrycket v till typen T static_cast<t>(v) Bör ej ta bort const-villkor static_cast<char*>(getvalue()) <-- där getvalue() returnerar const char* Ett uttryck e kan konverteras till T förutsatt att konverteringsoperationer är deklarerade static_cast<t>(e) <-- ger T t(e) där t är ett temporärt objekt A a = static_cast<a>('x'); blir A a('x'); men det kan ju kompilatorn klara implicit? Ger lite mer kontroll eftersom nu sker konvertering som DU vill Betyder detta att static_cast endast fungerar i de fall implicit konvertering fungerar? 04-05-25 10
static_cast Nej... den kan även göra följande konverteringar Från godtycklig typ till void Från en basklass till en subklass-referens Exempel static_cast<void*>(&b);// ekvivalent med (void*)&b; static_cast<int>(b); // ekvivalent med (int)b; eller int(b); Derived derived; Base& base = derived; static_cast<derived&>(base); Base base1; static_cast<derived&>(base1); <-- odef. beteende 04-05-25 11
static_cast Fortsättning exempel char* p; void* v; v = p; // OK p = v; // Error (ingen implicit konverting finns) p = static_cast<char*>(v)// OK (explicit konvertering) 04-05-25 12
dynamic_cast Navigera i en arvhierarki Run-time konvertering (polymorfism) Basklass Subklass Konvertering kan ske compile-time static_cast Konvertering kan endast ske run-time dynamic_cast 04-05-25 13
dynamic_cast Kan indikera om en konvertering misslyckats Konvertering av pekare 0 returneras vid felaktig konvertering Konvertering av referens Bad_cast-undantag kastas Exempel Base* base; Derived derived; base = &derived; Derived* d = dynamic_cast<derived*>(base); 04-05-25 14
const_cast Tar bort const från en typ Exempel Funktionen f() double f(double& d); f() vill anropas från funktionen g() void g(const double& d) { val = f(d); } Kompilatorn klagar eftersom f() kan ändra d void g(const double& d) { val = f(const_cast<double&>(d)); } 04-05-25 15
reinterpret_cast Konvertering mellan två helt orelaterade typer Kan användas för att konvertera en pekare till en int Kompilatorspecifikt beteende Minskar portabilitet av kod <-- bör undvikas! För mer information om explicit konvertering Bowden Wise, G.: Casting in C++: Bringing Safety and Smartness to Your Programs http://www.acm.org/crossroads/xrds3-1/ovp3-1.html (2004-04-28) 04-05-25 16
String-klassen Varför finns då inte en konverteringsoperator från string till char*? konverteringskonstruktor från char* string s = Vi älskar typkonvertering ; kopieringskonstruktor och tilldelningsoperator string t(s); string t = s; Om det då fanns en konvertering från string->char* string s = t; Är t en char* eller en string? Funktionen c_str() returnerar en char* 04-05-25 17
Virtuella arv Löser en del problem med multipel arv Vehicle Vehicle Car Jet JetCar 04-05-25 18
Virtuella arv Skapar endast en kopia av basklassen Vehicle virtual virtual Car Jet JetCar 04-05-25 19
Virtuella arv Klassen längst ned i hierarkin ansvarar för att initiera den virtuella basklassen I det vanliga fallet initierar subklassen klassen direkt ovan i hierarkin Exempel class Vehicle { Vehicle(string) { } }; class Car : virtual public Vehicle { }; class Jet : virtual public Vehicle { }; class JetCar : public Jet, public Car { JetCar(string) : Vehicle( call base class ) { } }; 04-05-25 20
Arvsdesign Bra arvsdesign är mycket svårt Typkonvertering nedåt i arvshierarki är oftast en indikation på dålig design Ett exempel på dålig arvsdesign Banksystem-exempel Delsystem som administrerar bankkonton 04-05-25 21
Banksystemet Tar fram en basklass class BankAccount { public: virtual ~BankAccount() = 0; virtual void deposit_money(double amount) = 0; virtual void withdraw_money(double amount) = 0; virtual double balance() = 0; }; Skapar även en array som håller alla konton BankAccount* all_accounts[num_accounts]; 04-05-25 22
Banksystemet I nuläget endast ett sorts konto Ett sparkonto class SavingsAccount : public BankAccount { public: // Lägger till månadens ränta void credit_interest(); //... }; Lägga till ränta till på alla sparkonton (ränterutin) for (int i = 0; i!= n; ++i) { all_accounts[i]->credit_interest(); } Men det fungerade ju inte? 04-05-25 23
Banksystemet Vi vet ju att det är ett SavingsAccount så då berättar vi det för kompilatorn for (int i = 0; i!= n; ++i) { static_cast<savingsaccount*>(all_accounts[i])->credit_interest(); } Vi har just gjort en down cast Ett nytt krav dyker upp: Systemet skall kunna hantera check-konton som också är räntebärande class CheckingAccount : public BankAccount { public: void credit_interest(); //... }; Vad händer nu med ränterutinen? 04-05-25 24
Banksystemet Arrayen all_accounts kommer nu ha både sparkonton och checkkonton Ränterutinen hanterar bara sparkonton i dagsläget Det löser vi enkelt tänker vi... for (int i = 0; i!= n; ++i) { if (/* all_acounts[i] pekar på ett SavingsAccount */) { static_cast<savingsaccount*>(all_accounts[i])->credit_interest(); } else { static_cast<checkingaccount*>(all_accounts[i])->credit_interest(); } } Detta blir en underhållsmardröm! 04-05-25 25
Banksystemet Olika lösningar på problemet: Identifiera vad sparkonton och checkkonton har gemensamt class InterestBearingAccount : public BankAccount { public: virtual void credit_interest() = 0; }; class SavingsAccount : public InterestBearingAccount {... }; class CheckAccount : public InterestBearingAccount {... }; for (...) if ( /* InterestBearingAccount */ ) {... } Begränsa kontolistan till att bara kunna innehålla InterestBearingAccount Finns ingen perfekt lösning... Om down cast måste ske använd dynamic_cast istället för static_cast, vilket då ger 04-05-25 26
Banksystemet for (int i = 0; i!= n; ++i) { BankAccount* a = all_accounts[i]; if (SavingsAccount* sa = dynamic_cast<savingsaccount*>(a)) sa->credit_interest(); else if (CheckingAccount* sa = dynamic_cast<checkingaccount*>(a) sa->credit_interest(); else // ERROR! } 04-05-25 27