Föreläsning 8 Undantag, konstruktorer, mallar TDDD86: DALP Utskriftsversion av föreläsning i Datastrukturer, algoritmer och programmeringsparadigm 24 september 2015 Tommy Färnqvist, IDA, Linköpings universitet 8.1 Innehåll Innehåll 1 Undantag 2 Konstruktion, tilldelning och flytt 2.1 Varför konstruktorer? . . . . . 2.2 Defaultkonstruktor . . . . . . 2.3 Typomvandlande konstruktor . 2.4 Kopieringskonstruktor . . . . 2.5 Kopieringstilldelningsoperator 2.6 Flyttsemantik . . . . . . . . . 3 1 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mallar . . . . . . . . . . . . . . . . . . 2 2 3 4 4 5 7 10 8.2 Undantag Problem: size vs capacity • Vad händer om klienten kommer åt ett element på plats större än size? list.get(7) – Nu tillåter listan detta och returnerar 0 ∗ Är det bra eller dåligt? Vad (om något) bör vi göra åt det? 8.3 Felutskrifter och sentinelvärden • Felutskrift – Skriv ut ett felmeddelande och avbryt programmet ∗ Lite väl drastiskt, programmet får ingen chans att återhämta sig från problemet • Sentinelvärde – Returnera ett speciellt värde som indikerar att något gått fel, t.ex. -1 ∗ Problemet är att alla heltal kan förekomma som värden i listan! 8.4 1 Previllkor • previllkor: Något din kod antar är sant när den anropas. – Ofta dokumenterat som en kommentar vid funktionens huvud /* * Returns the element at the given index. * Precondition: 0 <= index < size */ int ArrayList::get(int index) { return m_elements[index]; } – Att ange ett previllkor “löser” egentligen inte problemet, men det dokumenterar vårt val och varnar klienten. – Men om klienten inte lyssnat och använder ett dåligt index ändå? – Kan vi se till att klienten måste följa previllkoret? 8.5 Kasta undantag • throw expression; – Genererar ett undantag som kraschar programmet om det inte har kod för att hantera (catch) undantaget – I Java kan man bara kasta objekt som är Exceptions; i C++ kan alla typer av värden kastas (int, string, etc.) throw 0; throw new vector<double>; throw 3.14159; // Kasta en int // Kasta en vector<double> * // Kasta en double – Det finns en klass std::exception att använda try { // Gör något } catch(int myInt) { // Om koden kastar en int fortsätter exekveringen här } catch(const exception& e) { // Om koden kastar ett standardundantag fortsätter exekveringen här cout << "exception: " << e.what() << ’\n’; } catch(...) { // Specialsyntax för att fånga alla typer av undantag cout << "An unexpected error has ocurred!\n"; } 8.6 Standardundantag 8.7 2 Privat hjälpfunktion // I ArrayList.h private: void checkIndex(int i, int min, int max) const; // I ArrayList.cpp void ArrayList::checkIndex(int i, int min, int max) const { if (i < min || i > max) { throw std::out_of_range("Index " + std::to_string(i) + " out of range; (must be between " + std::to_string(min) + " and " + std::to_string(max) + ")"); } } 8.8 2 Konstruktion, tilldelning och flytt 2.1 Varför konstruktorer? Initiering vs tilldelning • Initiering är fundamentalt i C++ och skiljt från tilldelning – Initiering transformerar ett objekts initiala skräpdata till giltigt data. ∗ Definieras av typens konstruktor – Tilldelning ersätter existerande giltigt data med annat giltigt data ∗ Definieras av typens tilldelningsoperator // Initiering: Defaultkonstruktor Widget x; // Initiering: Kopieringskonstruktor Widget y(x); // Initiering: Kopieringskonstruktor (alternativ form) Widget z = x; // Tilldelning: Kopieringstilldelning z = x; 8.9 Notera • Det är inte alltid nödvändigt att definiera alla sorters konstruktorer och tilldelningsoperatorer. Om du inte gör det kommer kompilatorn att skapa en defaultversion åt dig. 8.10 2.2 Defaultkonstruktor Defaultkonstruktor ArrayList() = default; • En defaultkonstruktor är en konstruktor som anropas utan argument ArrayList list; // variabeldeklaration utan initierare • = default innebär att konstruktorn genereras av kompilatorn – medlemmar av enkel typ initieras endast om de har en initierare i sin deklaration – medlemmar av klasstyp initieras av någon konstruktor beroende på hur vi kodat, defaulkonstruktorn om inte annat • De icke-statiska datamedlemmarna initieras av respektive NSDMI (“non-static data member initializer”) int m_size = 0; int m_capacity = 10; int* m_elements = new int[m_capacity]; 8.11 3 Medlemsinitierarlista Förekommer inte i ArrayList men är en grundläggande konstruktion • En egen defaultkonstruktor hade kunnat skrivas ArrayList() : m_size{ 0 }, m_capacity { 10 }, m_elements { new int[m_capacity] } {} • Om deklarationen av en datamedlem har en initerare utförs den inte om det finns en medlemsinitierare • Medlemsinitierarlistan låter oss initiera (inte tilldela) datamedlemmar när vi initierar vår typ // Tilldelning struct Widget { const int value; Widget(); }; Widget::Widget() { value = 42; //ERROR } // Initiering struct Widget { const int value; Widget(); }; Widget::Widget() : value{42} {} 8.12 2.3 Typomvandlande konstruktor Typomvandlande konstruktor ArrayList::ArrayList(const vector<int>& v) { for (auto vi : v) { add(vi); } } • En konstruktor som kan anropas med ett argument av annan typ är en typomvandlande konstruktor ArrayList list(vector<int> {23, 24, 25, 26}); – syntaxen ovan kallas direktinitiering • Följande deklarationssyntax kallas kopieringsinitiering ArrayList a1 = a2; // samma typ - kopieringskonstruktorn gör initiering ArrayList a3 = vector<int>{1, 2, 3} // implicit typomvandling ArrayList a4 = ArrayList(vector<int>{1, 2, 3}) // explicit typomvandling – flyttkonstruktorn gör initieringen i de två senare fallen – optimering kan förekomma. . . • Alla explicita typomvandlingar sker med denna konstruktor, exempelvis auto a5 = static_cast<ArrayList>(vector<int> {23, 24, 25, 26}); 8.13 2.4 Kopieringskonstruktor Kopieringsinitiering I C++ kan kopieringsinitiering ske på tre olika sätt: 1. En variabel skapas som en kopia av ett existerande värde MyClass one; MyClass two = one; Koden ovan är ekvivalent med MyClass one; MyClass two(one); 2. Värdeöverföring av ett objekt till en funktion void myFunction(MyClass arg) { ... } MyClass mc; myFunction(mc); 4 3. Ett objekt returneras som värde från en funktion MyClass myFunction() { MyClass mc return mc; } 8.14 Kopieringskonstruktorn Kopieringsinitiering i C++ sker med hjälp av kopieringskonstruktorn: • Syntaktiskt skrivs kopieringskonstruktorn som en konstruktor som tar en parameter — en instans av klassen överförd som referens till const class MyClass { public: MyClass(); ~MyClass(); MyClass(const MyClass& other); // Copy constructor /* ... */ }; • Den kompilatorgenererade kopieringskonstruktorn gör medlemsvis kopiering. . . 8.15 Grund kopieringsbugg • Kopieringsinitiering av vår ArrayLista orsakar problem • En ändring i en lista påverkar den andra (dåligt!) list2.add(88); list1.remove(0); – När objekten förstörs frigörs minnet två gånger (dåligt!) 8.16 Djup kopiering • För att komma till rätta med kopieringsbuggen behöver vi skriva en kopieringskonstruktor som gör en djup kopiering av ArrayListan • Rule of Three: Om en klass har någon av följande tre medlemsfunktioner: – Destruktor – Kopieringskonstruktor – Kopieringstilldelningsoperator så bör den antagligen ha alla tre funktionerna. 8.17 Förbjud kopiering • En enkel åtgärd är att helt enkelt förbjuda kopiering // ArrayList.h ArrayList(const ArrayList& list) = delete; – Nu leder försök att kopiera till kompileringsfel – Löser problemet, men är kanske lite väl restriktivt 8.18 5 Kod för djup kopieringsinitiering // ArrayList.cpp ArrayList::ArrayList(const ArrayList& other) { m_capacity = other.m_capacity; m_size = other.m_size; m_elements = new int[m_capacity]; // deep copy for (int i = 0; i < m_size; i++) { m_elements[i] = other.m_elements[i]; } } 8.19 2.5 Kopieringstilldelningsoperator Kopieringstilldelning Tilldelning i C++ är mycket enklare än initiering och äger endast rum om ett existerande objekt explicit tilldelas ett nytt värde: MyClass one, two; MyClass two = one; • Jämför med följande kod där two initieras som en kopia av one MyClass one; MyClass two = one; 8.20 Kopieringstilldelningsoperatorn Kopieringstilldelning i C++ sker med hjälp av kopieringstilldelningsoperatorn: • Syntaktiskt är kopieringstilldelningsoperatorn mycket mer komplex än kopieringskonstruktorn eftersom det är en överlagrad operator class MyClass { public: MyClass(); ~MyClass(); MyClass(const MyClass& other); // Copy constructor MyClass& operator= (const MyClass& other); // Assignment operator /* ... */ }; • Den kompilatorgenererade kopieringstilldelningsoperatorn gör medlemsvis tilldelning. . . 8.21 Kod för djup kopieringstilldelning Koden för en korrekt kopieringstilldelningsoperator är mycket mer involverad än för kopieringskonstruktorn. • Till viss del beror detta på att C++ ger oss maximal flexibilitet. Till exempel är följande en giltig implementation: void MyClass::operator= (const MyClass& other) { cout << "I’m sorry, Dave. I’m afraid I can’t copy that object." << endl; } 8.22 Kod för djup kopieringstilldelning: version 1 /* Många stora felaktigheter här. Använd inte som referens! */ void ArrayList::operator= (const ArrayList& other) { m_capacity = other.m_capacity; m_size = other.m_size; m_elements = new int[m_capacity]; // deep copy for (int i = 0; i < m_size; i++) { m_elements[i] = other.m_elements[i]; } } • Koden baserad på kopieringskonstruktorn – Men, när tilldelningsoperatorn anropas har ArrayListan redan sitt eget fält av element, vilket betyder att vi har en minnesläcka. . . 8.23 6 Kod för djup kopieringstilldelning: version 2 /* Flera stora felaktigheter här. Använd inte som referens! */ void ArrayList::operator= (const ArrayList& other) { delete[] m_elements; m_capacity = other.m_capacity; m_size = other.m_size; m_elements = new int[m_capacity]; // deep copy for (int i = 0; i < m_size; i++) { m_elements[i] = other.m_elements[i]; } } • All kod efter delete[] är exakt likadan som den i kopieringskonstruktorn – Ingen slump — i de flesta fall kommer stort överlapp att finnas – Eftersom vi inte får anropa vår egen kopieringskonstruktor direkt (eller någon annan konstruktor för den delen) bryter vi ut kopieringskoden i en hjälpfunktion 8.24 Kod för djup kopieringstilldelning: version 3 void ArrayList::copyOther(const ArrayList& other) { m_capacity = other.m_capacity; m_size = other.m_size; m_elements = new int[m_capacity]; // deep copy for (int i = 0; i < m_size; i++) { m_elements[i] = other.m_elements[i]; } } ArrayList::ArrayList(const ArrayList& other) { copyOther(other); } /* Inte helt perfekt än. Använd inte som referens! */ void ArrayList::operator= (const ArrayList& other) { delete[] m_elements; copyOther(other); } • Vi har ett par saker kvar att ta hänsyn till – Betrakta följande kodsnutt: ArrayList one; one = one; 8.25 Kod för djup kopieringstilldelning: version 4 /* Inte helt perfekt än. Använd inte som referens! */ void ArrayList::operator= (const ArrayList& other) { if (this != &other) { delete[] m_elements; copyOther(other); } } • En sista bugg att ta hand om – Betrakta följande kodsnutt: ArrayList one, two, three; three = two = one; 8.26 Kod för djup kopieringstilldelning: slutlig version ArrayList& ArrayList::operator= (const ArrayList& other) { if (this != &other) { delete[] m_elements; copyOther(other); } return *this; } 8.27 7 2.6 Flyttsemantik Utan C++11 vector<string> ReadAllWords(const string& filename) { ifstream input(filename.c_str()); vector<string> result; result.insert(result.begin(), istream_iterator<string>(input), istream_iterator<string>()); return result; } • Hur effektiv är den här koden? 8.28 Utan C++11 8.29 Utan C++11 8.30 Utan C++11 8.31 Med C++11 vector<string> ReadAllWords(const string& filename) { ifstream input(filename.c_str()); vector<string> result; result.insert(result.begin(), istream_iterator<string>(input), istream_iterator<string>()); return result; } • Ingen ändring i koden. . . 8.32 8 Med C++11 8.33 Med C++11 8.34 Med C++11 8.35 Med C++11 8.36 Flyttsemantik • Kopieringssemantik (C++03): Kan duplicera ett objekt – Kopieringsskonstruktor och kopieringstilldelningsoperator • Flyttsemantik (C++11): Kan flytta ett objekt från en plats till en annan – Flyttkonstruktor och flytttilldelningsoperator • Flyttsemantik ger asymptotiskt bättre prestanda i många fall 8.37 Rvalue-referens • Syntax Type && • Referens till en temporär variabel • Representerar en variabel vars innehåll kan flyttas från en plats till en annan 8.38 9 Med C++11 /* Flyttkonstruktor */ ArrayList::ArrayList(ArrayList&& other) { m_elements = other.m_elements; m_size = other.m_size; m_capacity = other.m_capacity; other.m_size = 0; other.m_capacity = 10; other.m_elements = new int[other.m_capacity]; } /* Flytttilldelningsoperator */ ArrayList& ArrayList::operator= (ArrayList&& other) { if (this != &other) { delete[] m_elements; m_elements = other.m_elements; m_size = other.m_size; m_capacity = other.m_capacity; other.m_size = 0; other.m_capacity = 10; other.m_elements = new int[other.m_capacity]; } return *this; } 8.39 Flyttsemantik 6= kopieringssemantik • Kan fortfarande returnera objekt genom att kopiera • C++11 provar att flytta först, använder kopiering om det inte lyckas • Objekt kan vara flyttbara utan att vara kopierbara 8.40 3 Mallar Vad är en mall (template)? • En mallfunktion är en ritning för att generera funktioner • Det ekvivalent med att låta kompilatorn automatiskt skriva ut varje instans av funktionen för alla typer du behöver • Instansiering av mallen sker när en mallfunktion används för en specifik typ av variabel 8.41 Vad är en mall (template)? • För att deklarera en mallfunktion, lägg bara till följande rad före funktionens definition: template <typename T> • T är mallparametern, vilken kommer att ersättas med en specfik typ när du använder mallfunktionen. Den måste inte heta T. template<typename T> T min(T a, T b) { return (a < b) ? a : b; } 8.42 Validering av mallar • Kompilatorn “verifierar” instansieringen av en mallfunktion. • Mallfunktioner fungerar bara om alla operationer som används på variabeln av den mallifierade typen stöds av typen som instansieras. 8.43 Använda mallar • Hittills har vi bara använt mallar för att slippa upprepa samma funktion. • Mallar kan användas till mycket, mycket mer. 10 8.44 Klassmallar • Markera varje/klass funktion som mallifierade i .h- och .cpp-filerna • Ersätt föregående typen (t.ex. int) med T i koden 8.45 .h och .cpp för mallklasser • På grund av en egenhet i C++ mallsystem måste separationen mellan header och implementation reduceras. – Skriv antingen alla kroppar i .h-filen, – eller, inkludera .cpp-filen i slutet av .h-filen för att sammanfoga dem. 8.46 11
© Copyright 2024