PB přednáška (12. října 2015)

Podobné dokumenty
PB161 Programování v jazyce C++ Přednáška 4

PB161 Programování v jazyce C++ Přednáška 5

PB161 Programování v jazyce C++ Přednáška 5

PB161 Programování v jazyce C++ Přednáška 4

Více o konstruktorech a destruktorech

PB161 Programování v jazyce C++ Přednáška 8

Mělká a hluboká kopie

Programování v C++ VI

PB161 Programování v jazyce C++ Přednáška 2

PB161 Programování v jazyce C++ Přednáška 9

Programování v C++ 3, 3. cvičení

Programování v C++ 1, 5. cvičení

Programování v C++ 2, 4. cvičení

Konstruktory a destruktory

Pokročilé programování v jazyce C pro chemiky (C3220) Operátory new a delete, virtuální metody

C++ přetěžování funkcí a operátorů. Jan Hnilica Počítačové modelování 19

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Práce s polem a pamětí

Úvod Třídy Rozhraní Pole Konec. Programování v C# Hodnotové datové typy, řídící struktury. Petr Vaněček 1 / 39

Dynamická identifikace typů v C++.

Ukazatele, dynamická alokace

Generické programování

PB161 Programování v jazyce C++ Přednáška 9

IUJCE 07/08 Přednáška č. 6

PB161 Programování v jazyce C++ Přednáška 9

Programování v C++ 1, 6. cvičení

PŘETĚŽOVÁNÍ OPERÁTORŮ

konstruktory a destruktory (o)

Dědění, polymorfismus

Základy programování (IZP)

přetížení operátorů (o)

PB161 Programování v jazyce C++ Přednáška 7

Úvod do programovacích jazyků (Java)

Základy programování (IZP)

PREPROCESOR POKRAČOVÁNÍ

PB161 Programování v jazyce C++ Přednáška 7

Polymorfismus. Časová náročnost lekce: 3 hodiny Datum ukončení a splnění lekce: 30.března

Základy C++ I. Jan Hnilica Počítačové modelování 18

PB161 Programování v jazyce C++ Přednáška 3

Pokročilé programování v jazyce C pro chemiky (C3220) Třídy v C++

Jazyk C++ 1. Blok 3 Objektové typy jazyka C++ Třída. Studijní cíl. Doba nutná k nastudování. Průvodce studiem

2) Napište algoritmus pro vložení položky na konec dvousměrného seznamu. 3) Napište algoritmus pro vyhledání položky v binárním stromu.

Jazyk C# (seminář 6)

Chování konstruktorů a destruktorů při dědění

PB přednáška (23. listopadu 2015)

PB071 Programování v jazyce C Jaro 2015

PB161 Programování v jazyce C++ Přednáška 11

TŘÍDY POKRAČOVÁNÍ. Události pokračování. Příklad. public delegate void ZmenaSouradnicEventHandler (object sender, EventArgs e);

Programování v C++ 2, 8. cvičení

Správa paměti. Karel Richta a kol. Katedra počítačů Fakulta elektrotechnická České vysoké učení technické v Praze Karel Richta, 2016

Pokročilé programování v jazyce C pro chemiky (C3220) Statické proměnné a metody, šablony v C++

Datové typy v Javě. Tomáš Pitner, upravil Marek Šabo

PB161 Programování v jazyce C++ Přednáška 2

Jazyk C++ I. Polymorfismus

Obsah. Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15

<surface name="pozadi" file="obrazky/pozadi/pozadi.png"/> ****************************************************************************

Jazyk C++ I. Polymorfismus

PB161 Programování v jazyce C++ Přednáška 10

Ukazatele #2, dynamická alokace paměti

PB161 Programování v jazyce C++ Přednáška 10

Definice třídy. úplná definice. public veřejná třída abstract nesmí být vytvářeny instance final nelze vytvářet potomky

PB071 Programování v jazyce C Jaro 2013

Ukazatel (Pointer) jako datový typ - proměnné jsou umístěny v paměti na určitém místě (adrese) a zabírají určitý prostor (počet bytů), který je daný

Virtuální metody - polymorfizmus

Abstraktní třídy, polymorfní struktury

Martin Flusser. Faculty of Nuclear Sciences and Physical Engineering Czech Technical University in Prague. December 7, 2016

PB161 Programování v jazyce C++ Přednáška 11

Jazyk C++ I. Šablony 2

PB071 Programování v jazyce C Jaro 2017

Pokročilé programování v jazyce C pro chemiky (C3220) Pokročilá témata jazyka C++

Pointery II. Jan Hnilica Počítačové modelování 17

Správa paměti. doc. Ing. Miroslav Beneš, Ph.D. katedra informatiky FEI VŠB-TUO A-1007 /

8 Třídy, objekty, metody, předávání argumentů metod

Obsah přednášky 7. Základy programování (IZAPR) Přednáška 7. Parametry metod. Parametry, argumenty. Parametry metod.

V dalších letech se pak začaly objevovat první normy pro jazyk C++ (ISO/IEC 14882:1998; ISO/IEC 9899:1999; ISO/IEC 14882:2003; ISO/IEC 14882:2011).

Programování II. Návrh programu I 2018/19

24. listopadu 2013, Brno Připravil: David Procházka

přetížení operátorů (o)

Dynamická alokace paměti

Programování v C++, 2. cvičení

Iterátory v C++. int pole[20]; for (int *temp = pole, temp!= &pole[20]; temp++) { *temp = 0;

Ukazka knihy z internetoveho knihkupectvi

Programování v jazyce C a C++

int ii char [16] double dd název adresa / proměnná N = nevyužito xxx xxx xxx N xxx xxx N xxx N

Programování II. Polymorfismus

PROGRAMOVÁNÍ V C++ URČENO PRO VZDĚLÁVÁNÍ V AKREDITOVANÝCH STUDIJNÍCH PROGRAMECH ROSTISLAV FOJTÍK

PB161 Programování v jazyce C++ Objektově Orientované Programování

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

NMIN201 Objektově orientované programování 1 / :36:09

NPRG031 Programování II 1 / :25:46

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Programování v jazyce C a C++

IRAE 07/08 Přednáška č. 2. atr1 atr2. atr1 atr2 -33

SYSTÉMOVÉ PROGRAMOVÁNÍ Cvičení č.1

Ukazatele a pole. Chceme-li vyplnit celé pole nulami, použijeme prázdný inicializátor: 207 Čárka na konci seznamu inicializátorů

Základy programování 2 KMI/ZP2

Výčtový typ strana 67

Střední škola pedagogická, hotelnictví a služeb, Litoměříce, příspěvková organizace

Spojová implementace lineárních datových struktur

Střední škola pedagogická, hotelnictví a služeb, Litoměříce, příspěvková organizace

Transkript:

PB161 3. přednáška (12. října 2015) Poznámky k domácímu úkolu Dynamická alokace Statická vs. dynamická alokace statická alokace na zásobníku (stack) deklarace uvnitř bloku (lokální proměnné) automatické uvolnění paměti na konci bloku rychlé (zásobník v cache) zásobník má omezenou velikost využití pro krátkodobé a menší objekty dynamická alokace na haldě explicitní žádost o kus paměti explicitní pokyn k uvolnění paměti pro dlouhodobé nebo větší objekty statická alokace globálních proměnných (global scope) alokace před spuštěním programu uvolnění paměti po skončení programu týká se i statických proměnných ve funkcích globální proměnné je lepší moc nepoužívat toto vše byste měli už znát z C Dynamická alokace v C alokace pomocí malloc, dealokace pomocí free je třeba správně spočítat potřebný počet bajtů typicky něco jako počet prvků * sizeof(prvek) není typová kontrola (malloc vrací void *) pokud se alokace nepodaří, malloc vrací NULL alokovaná paměť není inicializovaná můžeme použít i v C++, ale v naprosté většině případů to není vůbec vhodné Dynamická alokace v C++ operátory new, delete, delete [] dva základní typy alokace alokace jednoho objektu / jedné hodnoty primitivního datového typu alokace pole objektů / pole hodnot zadáváme typ alokovaného objektu a případně počet potřebné místo je automaticky spočítáno // alokuje paměť o velikosti sizeof(int) int * ptr = new int; 1

// alokuje paměť o velikosti 20 * sizeof(int) int * array = new int[20]; při volání new je po alokaci volán konstruktor objektu i primitivní datové typy se mohou inicializovat int * ptr1 = new int; // neinicializovaná paměť int * ptr2 = new int(17); // paměť inicializovaná na 17 int * ptr3 = new int(); // paměť inicializovaná na 0 class A { int value; A() : value(42) { A(int v) : value(v) { int getvalue const () : { return value; ; A * ptra1 = new A; // volá bezparametrický konstruktor A() cout << ptra1->getvalue() << endl; // 42 A * ptra2 = new A(200); // volá konstruktor A(int) cout << ptra2->getvalue() << endl; // 200 všimněte si: u primitivních datových typů je rozdíl mezi new int a new int(), u složených datových typů (objektů) to znamená totéž není možno nechat objekt neinicializovaný při alokaci polí se vždy použije bezparametrický konstruktor u primitivních datových typů zůstává paměť neinicializovaná class A { /*... */ ; // jako předtím A * arraya = new A[17]; for (int i = 0; i < 17; ++i) { cout << arraya[i].getvalue() << endl; // vypíše sedmnáctkrát 42 int * array = new int[200]; // neinicializovaná paměť, před použitím je třeba nejdřív inicializovat class B { int value; B(int v) : value(v) { 2

; B * arrayb = new B[17]; // CHYBA! B nemá bezparametrický konstruktor pokud se alokace nepodaří, new vyhazuje výjimku std::bad_alloc o výjimkách se budeme bavit později varianta operátoru new bez vyhazování výjimek: new (nothrow) vrací nullptr (v C++03 NULL) int * array = new (std::nothrow) int[1000000]; if (array) { //... dealokace pomocí delete / delete [] první oprátor používáme pro samostatné objekty, druhý pro pole tohle kompilátor nepohlídá! proč? int * ptr = new int(17); //... delete ptr; int * array = new int[200]; //... delete [] array; problémy s alokací a dealokací memory leak (nedealokovaná paměť, na kterou již nemáme ukazatel) zápis do nealokované paměti volání delete místo delete [] a naopak volání delete na špatný ukazatel detekce problémů: program valgrind // tento program pravděpodobně spadne int main() { int * array = new int[100]; array += 42; delete array; // špatný ukazatel Dealokace a destruktory při dealokaci paměti se volá destruktor destruktor potomka volá na závěr automaticky destruktor předka statická dealokace (na konci bloku u lokálních proměnných, na konci programu u globálních proměnných) 3

dynamická dealokace (při zavolání delete) který destruktor se zavolá? class IPrinter { virtual void printdocument(std::string); ; class MyPrinter : public IPrinter { std::string * printerqueue; MyPrinter() : printerqueue(new std::string[10]) { void printdocument(std::string) override; ~MyPrinter() { delete [] printerqueue; ; int main() { IPrinter * p = new MyPrinter; p.printdocument("mydocument.doc"); delete p; // zavolá se ~IPrinter(), MEMORY LEAK! pokud píšeme třídu, od které se (a) bude dědit, (b) instance potomků budou dealokovány skrze ukazatele na předka, pak je velmi vhodné používat virtuální destruktor class IPrinter { //... virtual ~IPrinter() { ; class MyPrinter : public IPrinter { /*... */ ; int main() { IPrinter * p = new MyPrinter; //... delete p; // zavolá se ~MyPrinter(), OK Kopírování, kopírovací konstruktory Kopírování objektů 4

ke kopírování objektů může dojít při inicializaci z jiného objektu při přiřazení class Point { int x, y; Point(int sx, int sy) : x(sx), y(sy) { int getx() const { return x; int gety() const { return y; void setx(int sx) { x = sx; void sety(int sy) { y = sy; ; Point p1(11,21); Point p2 = p1; // kopírování při inicializaci Point p2(p1); // ekvivalentní zápis Point p2{p1; // ekvivalentní zápis v C++11 p2.getx(); // vrátí 11 p2.setx(17); p1.getx(); // vrátí opět 11, p1 a p2 jsou různé objekty Point p3(0,0); p3.getx(); // vrátí 0 p3 = p1; // kopírování při přiřazení p3.getx(); // vrátí 11 p1.setx(99); p3.getx(); // vrátí opět 11, p1 a p3 jsou různé objekty kopírování při inicializaci zajišťuje tzv. kopírovací konstruktor i když žádný nedefinujeme, automaticky se vyrobí implicitní má tedy někdy smysl definovat vlastní kopírovací kosntruktor? plytká (shallow) vs. hluboká (deep) kopie // tento příklad budeme postupně rozvíjet class IntArray { 5

size_t size; int * data; IntArray() : size(0), data(nullptr) { IntArray(size_t s) : size(s), data(new int[size]) { for (size_t i = 0; i < size; ++i) data[i] = 0; ~IntArray() { delete [] data; ; plytká kopie kopíruje pouze hodnoty atributů máme-li jako atribut ukazatel, kopíruje se jen jeho hodnota, ne to, na co ukazuje to může být problém int main() { IntArray arr1(10); IntArray arr2 = arr1; // na konci bloku se zavolá destruktor pro arr1 i arr2 // CHYBA! špatné použití delete na už uvolněný ukazatel! jedno řešení: zakázat kopírování vhodné pro objekty, které nemá smysl kopírovat v C++11 pomocí speciální syntaxe = delete v C++03 se řešilo přesunem kopírovacího konstruktoru do sekce private // uvnitř třídy IntArray IntArray (const IntArray &) = delete; druhé řešení: napsat si vlastní kopírovací konstruktor, který provede hlubokou kopii // uvnitř třídy IntArray IntArray (const IntArray & other) : size(other.size), data(new int[size]) { for (size_t i = 0; i < size; ++i) data[i] = other.data[i]; všimněte si typu kopírovacího konstruktoru, toto je jediná (rozumná) možnost 6

kopírovací konstruktor nemůže brát vstup hodnotou (při volání hodnotou se vyrábí kopie právě pomocí kopírovacího kosntruktoru) kdyby bral kopírovací konstruktor nekonstantní referenci, mohl by měnit kopírovaný objekt, to by bylo velmi nečekané // nyní můžeme psát IntArray arr1(10); IntArray arr2 = arr1; // arr1 a arr2 jsou teď dva různé objekty a různé jsou i // jejich ukazatele data, // každý ukazuje na jinou paměť o velikosti 10 * sizeof(int) už jsme tím vyřešili veškeré problémy s kopírováním? int main() { IntArray arr1(10); IntArray arr2(20); arr2 = arr1; // kopírování při přiřazení // dva problémy: // * na konci je arr2.data = arr1.data // (stejný problém jako minule) // * původní arr2.data se neuvolní (memory leak) je třeba ještě vyřešit kopírování při přiřazení to řeší speciální metoda zvaná operator= pozn. ve skutečnosti můžeme definovat i mnohé další operátory, podrobně to probereme až v jedné z pozdějších přednášek typ operátoru přiřazení může být různý, jedna možnost je Object & operator=(const Object &) už je nám jasné, proč se parametr bere konstantní referencí návratový typ může být i jiný, např. void proč mít jako návratový typ přiřazovacího operátoru referenci na objekt? je to konzistentní se způsobem, jak přiřazovací operátor funguje pro primitivní datové typy, což umožňuje řetězené přiřazení typu a = b = c = 1 některé části standardní knihovny toto chování očekávájí doporučení: pokud nemáte nějaký zvláštní důvod to dělat jinak, vracejte v operátoru přiřazení vždy referenci na objekt // uvnitř třídy IntArray IntArray & operator=(const IntArray & other) { delete [] data; size = other.size; 7

data = new int[size]; for (size_t i = 0; i < size; ++i) { data[i] = other.data[i]; return *this; // vracíme referenci // na tento objekt // tento kód má několik drobných problémů problém se sebepřiřazením (self-assignment) nemusí to nastat často, ale může se stát, že někdo napíše a = a v tom případě dojde ve výše uvedeném kódu k chybě, proč? bývá zvykem detekovat sebepřiřazení // uvnitř operátoru= třídy IntArray // detekce sebepřiřazení if (this == &other) return *this; kód v operátoru = částečně duplikuje kód z kopírovacího konstruktoru nešlo by toho nějak využít? odpovědí je tzv. copy-and-swap idiom // uvnitř třídy IntArray, alternativní možnost operátoru = void swap(intarray & other) { // prohodí "vnitřnosti" tohoto objektu a other using std::swap; swap(size, other.size); swap(data, other.data); // všimněte si volání hodnotou, které způsobí kopírování IntArray & operator=(intarray other) { swap(other); return *this; copy-and-swap idiom má různé výhody neduplikujeme kód dává prostor kompilátoru k optimalizaci (za jistých podmínek se kopie nemusí vůbec provést) ale může mít i drobné nevýhody při sebepřiřazení se zbytečně dělá kopie (to nám ale nemusí moc vadit, sebepřiřazení je vzácné) v původním kódu jsme mohli využít situace, kdy je velikost obou polí stejná 8

// uvnitř třídy IntArray, alternativní možnost operátoru = IntArray & operator=(const IntArray & other) { if (this == &other) return *this; if (size!= other.size) { delete [] data; size = other.size; data = new int[size]; // pokud jsou velikosti stejné, nemusíme // nic znova alokovat, prostě přepíšeme hodnoty for (size_t i = 0; i < size; ++i) { data[i] = other.data[i]; return *this; obecné doporučení (co je lepší?) používejte copy-and-swap (výhody výrazně ve většině případů výrazně převažují nad nevýhodami) až pokud zjistíte (profiling), že kopírování vás stojí moc času a je úzkým hrdlem vašeho programu, pak teprve přemýšlejte o tom, že byste to přepsali jinak abychom dokončili implementaci třídy IntArray, hodil by se nám ještě způsob, jak přistupovat k prvkům pole proto si zde ukážeme implementaci ještě jednoho operátoru podrobnější komentář k tomu, proč to děláme takhle, přijde jindy ale můžete o tom zkusit přemýšlet už teď // uvnitř třídy IntArray // přístup pro čtení i zápis int & operator[](size_t index) { return data[index]; // přístup jen pro čtení const int & operator[](size_t index) const { return data[index]; Rule of three ještě se vrátíme ke kopírovacím konstruktorům pravidlo tří: pokud vaše třída definuje jednu z následujích věcí, pravděpodobně chcete ve skutečnosti definovat všechny tři: destruktor kopírovací konstruktor kopírovací přiřazovací operátor 9

zdůvodnění: třída, která definuje jednu z těchto tří věcí, zřejmě spravuje nějaký prostředek (paměť, soubor, apod.) a kompilátorem automaticky generovaný destruktor/kopírovací konstruktor/přiřazovací operátorem pak zřejmě nedělá to, co chcete v C++11 Rule of five (C++11 má kromě kopírování i přesunování move semantics, více o tom ve zvláštní přednášce) Přetypování (casting) v C++ Přetypování v C jistě si vzpomínáte na přetypování v C (syntax (typ)hodnota) to je sice možné použít v C++, ale je to velmi nedoporučované zejména proto, že to není typově korektní máte striktně zakázáno používat přetypování ve stylu C Přetypování v C++ static_cast syntaxe static_cast<typ>(hodnota) přetypování, které je možno provést staticky (během kompilace) typické použití: primitivní datové typy (často ale funguje implicitně a není třeba uvádět static_cast změna celočíselné hodnoty na enum změna ukazatele na void * a naopak přetypování v nevirtuální objektové hierarchii v tomto předmětu se nejspíše setkáte jen s prvními dvěma body nemusí být vždy bezpečné nedefinované chování, např. pokud přetypujete hodnotu 3 na enum, který má jen dvě konstanty Přetypování v C++ dynamic_cast víme, že ukazatel na potomka se umí automaticky přetypovat na ukazatel na předka (a obdobně s referencemi) class A { /*... */ ; class B : public A { /*... */ ; A * ptr = new B; dynamic_cast umožňuje bezpečně přetypovat opačným směrem funguje jen pokud je objektová hierarchie virtuální (tj. třída předka má některou metodu virtuální) proč myslíte, že to tak je? B * ptr2 = dynamic_cast<b *>(ptr); rozhoduje se až za běhu programu 10

pokud ptr ve skutečnosti ukazuje na objekt typu B, přetypování se provede pokud ptr ukazuje na něco jiného, výsledkem je nullptr (v C++03 NULL) funguje i s referencemi protože nemáme žádnou null referenci, hází v druhém případě výjimku std::bad_cast použití dynamic_cast se často dá vyhnout vhodnou změnou objektové hierarchie, ale někdy se hodí např. při řešení úkolu na cvičení tento týden Další způsoby přetypování v C++ existují i další operátory přetypování v C++, ale záměrně o nich nebudeme mluvit mají svá specifická použití, ale narušují typovou bezpečnost, což typicky nechceme 11