Stáhnout prezentaci
Prezentace se nahrává, počkejte prosím
ZveřejnilEla Bláhová
1
cvičení Filip Zavoral
2
Docházka ◦ aktivní účast, znalost předchozí látky ◦ 3 nepřítomnosti OK, déledobější domluvit předem DÚ ◦ uprostřed semestru jedna větší domácí úloha Zápočtový program ◦ do 15.11. schválené zadání ◦ do 30.4. první pokus o odevzdání hotové verze ◦ do konce LS komplet hotovo vč. doc (jinak neuznáno) Zápočtový test ◦ během zimního zkouškového období v labu ◦ 3 termíny (3. termín během LS)
3
#include int main() { std::cout << "Hello world" << std::endl; return 0; }
4
#include int main() { std::cout << "Hello world" << std::endl; return 0; } VS 2013 File / New / Project Installed / Templates / VC++ / Win32 Win32 Console Application Name, Location - OK !!! Application Settings Console Application vypnout! Precompiled header vypnout! SDL zapnout! Empty project Solution Explorer Solution / Project Source Files Add New/Existing Item Visual C++ / C++ File (.cpp) (Header Files) ctrl-shift-B F5 ctrl-F5 F10 F11 F9 Debug / Window Watch, Auto, Locals Call Stack
5
Násobilka ◦ funkce (třída, metoda) ◦ parametr #include int main() { std::cout << "Hello world" << std::endl; return 0; } Násobilka 7: 1 * 7 = 7 2 * 7 = 14... 10 * 7 = 70 void fnc( int x) {.... } int main() { fnc( 7); return 0; }
6
#include using namespace std; int delkaretezce( const string& s) {... } void zpracuj( const vector & a) {... a[i]... } int main( int argc, char ** argv) { vector arg( argv, argv+argc); if ( arg.size() > 1 && arg[1] == "--help" ) { cout << "Usage: myprg [OPT]... [FILE]..." << endl; return 8; } zpracuj( arg); return 0; } deklarace knihovních funkcí rozbalení prostoru jmen std předávání parametrů odkazem konstantní reference ! Solution Explorer / Project Properties / Config Properties Debugging / Command Arguments vektor pro komfortnější zpracování ošetření parametrů příkazové řádky výkonná funkce / metoda v mainu nikdy nic užitečného přístup k prvkům vectoru
7
#include using namespace std; void vypis( const vector & a) { for( int i = 0; i < a.size(); ++i) { cout << "[" << a[ i] << "]"; } cout << endl; } int main( int argc, char ** argv) { vector arg( argv, argv+argc); if ( arg.size() < 2) { cout << "Usage: myprg parameters" << endl; return 8; } vypis( arg); return 0; }... si naprogramujte sami vypsat násobilku všech čísel z parametrů příkazové řádky int stoi ( const string& s);
8
#include int strtoint( const string& s) { stringstream ss(s); int n; ss >> n; return n; } stream z řetězce if( c >= '0' && c <= '9') if( isdigit( c)) int n = c - '0'; if( c >= 'a' && c <= 'z') písmena nejsou uspořádaná !! OK - čísla jsou uspořádaná isdigit je lepší num = num + c - 48; '0' ≉ 48 #include isalpha( c) isalnum(c) #include int stoi ( s, size_t& idxRet = nullptr, int base = 10); stol, stoul, stoll, stof, stod,... string to_string ( val); konverze čísel a stringů nepovinné parametry: - první nezkonvertovaný znak (reference - návratový parametr) - soustava
9
char a[] = "ahoj"; char* b = a; char* b = "ahoj"; string c = a string c = "ahoj"; string d = c + b + a; int atoi( char* s); atoi( b) atoi( c) atoi( a.c_str()) inicializovaná instance třídy ukazatel na C-string pole znaků ≈ C-string přetížené operátory 'A''h''o''j''\0' b a
10
int cele_jmeno( char * buf, size_t bufsize, const char * jm, const char * prijm) { size_t lj = strlen( jm); size_t lp = strlen( prijm); if ( lj + lp + 2 > bufsize ) { /* error */ return -1; } memcpy( buf, jm, lj); buf[ lj] = ' '; memcpy( buf + lj + 1, prijm, lp); buf[ lj + lp + 1] = 0; return lj + lp + 1; } string cele_jmeno( const string& jm, const string& prijm) { return jm + " " + prijm; } Jméno, Příjmení ⇓ Jméno Příjmení
11
class Pocitadlo { public: Pocitadlo( void); ~Pocitadlo(); int pocet_pismen( void); private: int pocet_; bool ve_slove_; }; #include #include "pocitadlo.hpp" int Pocitadlo::pocet_pismen( void) { char c;... while( cin.get(c)) { if( ve_slove_) { if( isspace( c)) {... } deklarace třídy -.hpp definice metod -.cpp konstruktor (defaultní), destruktor deklarace veřejných metod - rozhraní privátní data a metody - implementace include deklarace další typy, inicializace čtení po znacích typ znaku - asalpha, isdigit, isupper,...
12
Spočtěte ◦ počet znaků, řádek, slov, vět ◦ počet a součet čísel
13
Spočtěte ◦ počet znaků, řádek, slov, vět ◦ počet a součet čísel Upřesnění zadání ◦ zdroj dat: cin, obecný istream ◦ co to je slovo, věta ◦ různá funkčnost vs. neopakovatelný vstup Postup ◦ 1. funkční návrh ◦ 2. objektový návrh, rozhraní ! - encapsulace ◦ 3. implementace
14
int pocet_znaku; int pocet_slov; void spocitej(...) { for(;;) { c = s.get();.... if( isspace( c)) { pocet_slov =... if(...)... } void spocitej(...) { int pocet_znaku; int pocet_slov; for(;;) { c = s.get();.... if( isspace( c)) { pocet_slov =... if(...)... } class Ovecky { public: void spocitej(...); private:.... }; void Ovecky::spocitej(...) { int pocet_znaku; int pocet_slov; for(;;) { c = s.get();.... if( isspace( c)) { pocet_slov =... if(...)... }
15
class Ovecky { public: void zpracuj_znak( char c); void spocitej(...); int pocet_znaku() { return..; } int pocet_slov() { return..; } private: int pocet_znaku_; int pocet_slov_; }; void Ovecky::zpracuj_znak( char c) { if( isspace( c)) { pocet_slov_ =... if(...)... } void Ovecky::spocitej( istream& s) { for(;;) { c = s.get();.... zpracuj_znak( c); } int main() { Ovecky ov; ov.spocitej( cin);... } int main() { Ovecky ov; for(;;) { c = cin.get(); ov.zpracuj_znak( c); mach_etwas( c); }... }
16
#include "ovecky.h" void Ovecky::zpracuj_znak(...) {... } void Ovecky::spocitej( istream& s) {... } #include #include "ovecky.h" int main() { Ovecky ov; ov.spocitej( cin); cout << ov.pocet(); } #include #ifndef OVECKY_H_ #define OVECKY_H_ class Ovecky { public: void zpracuj_znak( char c); void spocitej( std::istream& s); int pocet_znaku() { return..; } int pocet_slov() { return..; } private: int pocet_znaku_; int pocet_slov_; }; #endif ovecky.h ovecky.cpp main.cpp guard inline deklarace implementace
17
Spočtěte ◦ počet znaků, řádek, slov, vět, počet a součet čísel ◦ číslo: posloupnost číslic obklopená mezerovými znaky ◦ slovo: posloupnost nemezerových znaků, která není číslo ◦ mezerové znaky (white spaces): ' ', '\t', '\n', '\r' ◦ řádky jen ty, kde je (alespoň) slovo nebo číslo poslední řádka nemusí být ukončená '\n' ◦ každá započítaná věta obsahuje alespoň 1 slovo '...' ani '31.12.2013' nejsou tři věty ◦ spočítat ze std vstupu nebo ze všech souborů uvedených na příkazové řádce ◦ objektově a modulárně hezky všichni poslat! kdo nestihne na cvičení, dodělat doma
18
include fce( istream& s) { char c; for(;;) { c = s.get(); if( s.fail()) return; process( c); } (pokus o) načtení jednoho znaku (nemusí se povést) jakýkoliv vstupní stream (cin, soubor, řetězec,...) detekce jakékoliv chyby (např. EOF) platná načtená hodnota fce( cin); ifstream f; f.open( "file.txt"); if( ! f.good()).... fce( f); zpracování std vstupu zpracování souboru for( i = 0; i < 10; i++) {... } jaký je rozdíl? co je lepší? for( i = 0; i < 10; ++i) {... }
19
class Trida { int fce( int x); }; { Trida objekt; objekt.fce( 1); } int Trida::fce( int x) {... } Trida objekt = new Trida();. operátor přístupu k položce objektu nalevo vždy proměnná :: kvalifikátor nalevo vždy typ
20
class Trida { std::string getResult () { return r; } std::string slozitaFce( int x); int jinaFce( int x) { int y = -1; for( i = 0; i < 10; ++i).... } }; #include "trida.h" { Trida ob; string s; s = ob.getResult(); s = ob.slozitaFce( 1); int z = jinaFce( 2); } #include "trida.h" string Trida::slozitaFce( int x) { int y; for( i = 0; i < 10; ++i) {.... } s = ob.r trida.h trida.cpp push 1 s = call ob.slozitaFce inline metoda rozvinutí místo volání s = ob.r předání parametrů a volání push 1 s = call ob.slozitaFce add esp, 8 push 2 y = -1 i = 0 loop: if( i >= 10) goto...... ++i goto loop ale: šablony! nutná definice při kompilaci ⇒ vše v headeru
21
vector - pole prvků s přidáváním zprava ◦ celočíselně indexováno, vždy od 0 ◦ všechny prvky umístěny v paměti souvisle za sebou ◦ při přidání možná změna lokace, neplatnost iterátorů! ◦ odvozené: queue, stack deque [dek] - fronta s přidáváním a odebíráním z obou stran ◦ double-ended queue ◦ prvky nemusí být umístěny v paměti souvisle ◦ lze přidávat i doleva list - obousměrně vázaný seznam ◦ vždy zachovává umístění prvků ◦ nepodporuje přímou indexaci forward_list - jednosměrně vázaný seznam basic_string - posloupnost ukončená terminátorem ◦ string, wstring array - pole pevné velikosti C++11 [dekjú] ≈ dequeue odebrat z fronty C++11 vector vi; list ls; array am;
22
setříděné ◦ setříděné podle operátoru < ◦ pro neprimitivní typy (třídy) nadefinovat operator< ◦ set - množina ◦ multiset - množina s opakováním ◦ map - asociativní pole - parciální zobrazení K -> T ◦ multimap - relace s rychlým vyhledáváním podle klíče K ◦ pair - pomocná šablona - uspořádané dvojice položky first, second šablona funkce make_pair( f,s) nesetříděné ◦ unordered_set/multiset/map/multimap ◦ hash table - nesetříděné, vyhledávání pouze na == ◦ pro neprimitivní typy (třídy) nadefinovat hashovací funkci size_t hash (const X &) C++11
23
polootevřený interval
24
Iterátor ◦ objekt reprezentující odkazy na prvky kontejneru ◦ operátory pro přístup k prvkům ◦ operátory pro procházení kontejneru kontejner ::iteratoriterátor příslušného kontejneru ◦ iterátor je typovaný kontejner ::const_iterator konstantní iterátor - používejte! *it, it->xpřístup k prvku/položce přes iterátor ++it posun na následující prvek +(int) -(int)posun iterátoru begin(), end()iterátor na začátek / za(!) konec kontejneru vector pole = { 10, 11, 20 }; vector ::const_iterator i; for( i = pole.begin(); i != pole.end(); ++i) cout << *i; const vector::iterator ≠ vector::const_iterator
25
jednotné rozhraní nezávislé na typu kontejneru ALE: ne všechny kontejnery podporují vše! push_back(T), push_front(T)přidání prvku na konec / začátek pop_front(), pop_back()odebrání ze začátku / konce ◦ nevrací hodnotu, jen odebírá! front(), back()prvek na začátku / konci operator[], at()přímý přístup k prvku ◦ bez kontroly, s kontrolou (výjimka) insert (T), (it, T) vložení prvku, před prvek insert (it, it b, it e)vložení intervalu insert(make_pair(K,T)) vložení do mapy - klíč, hodnota erase(it), erase(it,it)smazání prvku, intervalu find(T)vyhledání prvku size(), empty() velikost / neprázdost clear()smazání kontejneru upper_bound, lower_boundhledání v multisetu/mapě ◦... and many many others
26
#include.. map, unordered_map,.. vector pole = { 10, 11, 20 }; pole.push_back( 30); x = pole[3]; vector ::const_iterator i; for( i = pole.begin(); i != pole.end(); ++i) cout << "[" << *i << "]"; map m; m.insert( make_pair( "jedna", 1)); vector pole; for( auto i : pole) cout << "[" << i << "]"; vector pole; for( const auto& i : pole) cout << "[" << i << "]"; Initializers (C++11) Přidání na konec Kruliš 2014: rozsahem řízený cyklus Typicky: konstantní reference Cyklus s iterátory Přidání do mapy Vytvoření pairu Range-based for vector pole; for( auto i = pole.begin(); i != pole.end(); ++i) cout << "[" << *i << "]"; Typová inferece
27
add slovo cizi del slovo cizi del slovo find slovo -> cizi cizi cizi map mapa; map ::const_iterator it; fce( map & mm); typedef map Mapka; Mapka::const_iterator it; fce( const Mapka& mm); neopisujte stále deklarace ! Proč: -neupíšu se -změna druhu nebo typu -rozlišení logicky různých typů -čitelnost Překladový slovník (k jednomu slovu může být více překladů) -přidat slovo a jeho překlad(y) -odebrat jeden překlad slova -odebrat všechny překlady slova -nalézt všechny překlady slova -nalézt všechny překlady slov začínajících prefixem -nalézt slovo když znáte překlad pfind slovo -> slovoxxx cizi cizi slovoyyy cizi slovozzz cizi cizi rfind cizi -> slovo slovo
28
složitostpřidání / odebrání na začátku přídání / odebrání na i-té pozici přídání / odebrání m prvků přídání / odebrání na konci nalezení i-tého prvku funkce push_front pop_front insert erase insert erase push _ back pop_back begin() + i [i] list konst m, konst přesuny mezi sezn. (splice) konstneex deque konstmin( i, n - i)m + min( i, n - i) konst vector neexn - im + n - ikonst asocia tivní ln (s klicem k) ln (s klicem k) ln + m ln nalezení podle hodnoty ln unsorted konst m
31
Do you need to be able to insert a new element at an arbitrary position in the container? you need a sequence container: associative containers won't do What category of iterators do you require? If they must be random access iterators... you're limited to vector, deque, and string Is it important to avoid movement of existing container elements when insertions or erasures take place? you'll need to stay away from contiguous-memory containers
32
Is lookup speed a critical consideration? you'll want to look at hashed containers (unordered_map), sorted vectors, and the standard associative containers — probably in that order Do you need to minimize iterator, pointer, and reference invalidation? Use node-based containers, because insertions never invalidate any references (unless they point to an element you are erasing). Insertions or erasures on contiguous-memory containers may invalidate all references into the container.
33
prolezeni pole 5 prvků dopředu pozpátku úkol: načíst z cin a vypsat odzadu po dvou, pak zase zepředu vector v;... vector ::const_iterator i; for( i = v.begin(); i != v.end(); ++i) cout << *i << " "; vector ::reverse_iterator i; for( i = v.rbegin(); i != v.rend(); ++i) cout << *i << " "; 1 2 3 4 5 6 7 7 5 3 1 2 4 6 vector pole;.... x = pole.size(); pole[x] = 0; co je zde špatně?
34
opatrně ◦ pozor na korektnost mnohem inteligentnější řešení ◦ rovnou při čtení rozhazovat na strany do deque (nebo list) void vypis( vector & v) { vector ::const_iterator i; i = v.end(); if( i == v.begin()) return; --i; for(;;) { cout << *i << ", "; if( i == v.begin() || i-1 == v.begin()) break; i -= 2; } if( i == v.begin()) ++i; // vytisteno [0] -> [1] else --i; // vytisteno [1] -> [0] for(;;) { cout << *i << "; "; if( i+1 == v.end() || i+2 == v.end()) break; i += 2; } cout << endl; }
35
vector x; type inference for( int i = 0; i < x.size(); ++i).. x[i].. vector ::const_iterator i; for( i = x.begin(); i != x.end(); ++i).. *i.. for( auto i = x.begin(); i != x.end(); ++i).. *i.. for( auto i : x).. i.. const auto& i : xchci jen prohlížet auto& i : xpotřebuji měnit auto i : xopravdu potřebuji kopii explicittype i : xpotřebuji přetypovat C++11 range-based for jednotný průchod kontejnery jen pro vector/array reference na prvek int y[MAX]; for( int *i = y; i != y+MAX; ++i).. *i.. historie: pointrová aritmetika
36
#include using namespace std; int main() { string s; vector v; for(;;) { cin >> s; if( cin.fail()) break; v.push_back(s); } sort( v.begin(),v.end()); for(.... ) cout <<....; cout << endl; } list v; list ::const_iterator i; for(;;) { cin >> s; if( cin.fail()) break; for( i = v.begin(); i != v.end() && *i <= s; ++i) ; v.insert( i, s); } string s; set v; for(;;) { cin >> s; if( cin.fail()) break; v.insert(s); } bool mysort( const string& s1, const string& s2) { return s1.size() < s2.size() ? true : (s1.size() > s1.size() ? false : s1 < s2) } set v;... v.insert(s); vlastní třídění jak to setřídit?
37
#include it find( it first, it last, T&) int count( it first, it last, T&) for_each( it first, it last, fnc( T&)) transform( it first, it last, output_it out, fnc( T&)) copy( it first, it last, output_it out) sort( begin, end, sort_fnc(x&, y&)) find_if, count_if, remove_if( it first, it last, pred& p) funkce modifikuje argument vrací modifikovaný argument možnost jiného kontejneru predikát: bool fnc( const T&)
38
#include vector v = { 1, 3, 5, 7, 9 }; // vector ::const_iterator result; auto result = find( v.begin(), v.end(), 5); bool greater10 ( int value ) { return value >10; } result = find_if( v.begin( ), v.end( ), &greater10 ); if ( result == v.end( ) ) cout << "Nothing" << endl; else cout << "Found: " << *result << endl; predikát for_each( begin, end, fnc( T&)) // vynásobit všechny prvky 2 // přičíst ke všem prvkům +1, +2, +3,... void mul2( int& x) { x *= 2; } vždy otestovat!
39
#include vector v = { 1, 3, 5, 7, 9 }; // vector ::const_iterator result; auto result = find( v.begin(), v.end(), 5); bool greater10 ( int value ) { return value >10; } result = find_if( v.begin( ), v.end( ), &greater10 ); if ( result == v.end( ) ) cout << "Nothing" << endl; else cout << "Found: " << *result << endl; for_each( begin, end, fnc( T&)) // vynásobit všechny prvky 2 // přičíst ke všem prvkům +1, +2, +3,... int fce( int& x) { static int qq = 0; return x += (qq +=1); } for_each( v.begin(), v.end(), fce); for_each( v.rbegin(), v.rend(), fce); jak parametricky? jak zrestartovat? jak krok parametricky?
40
class ftor { public: ftor( int step) : step_(step), qq_(0) {} int operator() (int& x) { return x += (qq_ += step_); } private: int step_; int qq_; }; for_each( v.begin(), v.end(), ftor(2)); it = find_if( bi, ei, fnc); class cmp { public: cmp( int cmp) : n_(cmp) {} bool operator() (int& x) { return x > n_; } private: int n_; }; auto fnd = find_if( v.begin(), v.end(), cmp(9)); cout << ( ( fnd == v.end()) ? -1 : *fnd) << endl; Přičíst ke všem prvkům +n, +2n, +3n,... najít v kontejneru prvek větší než n Funktor – třída s přetíženým operátorem () oddělení inicializace a běhového parametru najít prvek odlišný od předchozího alespoň o n inkrementovat čísla v zadaném rozsahu hodnot (první +1, druhé +2,...) najít číslo za největší dírou (rozdíl sousedních hodnot)
41
class scitacka { public: scitacka( int limit) : limit_(limit), vysledek_(0) {} int operator() (int& x) { if( x > limit_) vysledek += x; } int vysledek_; private: int limit_; }; scitacka s = for_each( v.begin(), v.end(), scitacka(10)); cout << s.vysledek_ << endl; součet všech čísel větších než parametr jak získat výsledek? po skončení hodnota použitého funktoru pozor! nejde o identický objekt
42
najděte všechny prvky větší než 9 lambda výrazy ◦ od C++ 11 mnohem jednodušší zápis a syntaxe vnitřní stav funktor find_if( v.begin(), v.end(), bind2nd( greater (), 9)); find_if( v.begin(), v.end(), greater_than( 9)); find_if( v.begin(), v.end(), [](int& x) { return x > 9; }); lambda výraz vlastní funktor
43
for_each() Performs an operation for each element count() Returns the number of elements count_if() Returns the number of elements that match a criterion min_element() Returns the element with the smallest value max_element() Returns the element with the largest value find() Searches for the first element with the passed value find_if() Searches for the first element that matches a criterion search_n() Searches for the first n consecutive elements search() Searches for the first occurrence of a subrange find_end() Searches for the last occurrence of a subrange find_first_of() Searches the first of several possible elements adjacent_find() Searches for two adjacent elements that are equal equal() Returns whether two ranges are equal mismatch() Returns the first elements of two sequences that differ lexicographical_compare() Returns whether a range is lexicogr. less
44
for_each() Performs an operation for each element copy() Copies a range starting with the first element copy_backward() Copies a range starting with the last element transform() Modifies and copies elements; combines two ranges merge() Merges two ranges swap_ranges() Swaps elements of two ranges fill() Replaces each element with a given value fill_n() Replaces n elements with a given value generate() Replaces each element with the result of an operation generate_n() Replaces n elements with the result of an operation replace() Replaces elements that have a special value replace_if() Replaces elements that match a criterion replace_copy() Replaces elements that have a special value while copying the whole range replace_copy_if() Replaces elements that match a criterion while copying the whole range
45
remove() Removes elements with a given value remove_if() Removes elements that match a given criterion - does not remove anything! remove_copy() Copies elements that do not match a value remove_copy_if() Copies elements that do not match a given criterion unique() Removes adjacent duplicates unique_copy() Copies elements while removing adjacent duplicates
46
reverse() Reverses the order of the elements reverse_copy() Copies the elements while reversing rotate() Rotates the order of the elements rotate_copy() Copies the elements while rotating their order next_permutation() Permutes the order of the elements prev_permutation() Permutes the order of the elements random_shuffle() Brings the elements into a random order partition() Changes the order of the elements so that elements that match a criterion are at the front stable_partition() Same as partition(), but preserves the relative order of matching and nonmatching elements
47
sort() Sorts all elements stable_sort() Sorts while preserving order of equal elements partial_sort() Sorts until the first n elements are correct partial_sort_copy() Copies elements in sorted order nth_element() Sorts according to the nth position partition() Changes the order of the elements so that elements that match a criterion are at the front stable_partition() Same as partition(), but preserves the relative order of matching and nonmatching elements make_heap() Converts a range into a heap push_heap() Adds an element to a heap pop_heap() Removes an element from a heap sort_heap() Sorts the heap (it is no longer a heap after the call)
48
binary_search() Returns whether the range contains an element includes() Returns whether each element of a range is also an element of another range lower_bound() Finds the first element greater than or equal to a value upper_bound() Finds the first element greater than a given value equal_range() Returns the range of elements equal to a given value merge() Merges the elements of two ranges set_union() Processes the sorted union of two ranges set_intersection() Processes the sorted intersection of two ranges set_difference() Processes a sorted range that contains all elements of a range that are not part of another set_symmetric_difference() Processes a sorted range that contains all elements that are in exactly one of two ranges inplace_merge() Merges two consecutive sorted ranges
49
accumulate() Combines all element values (processes sum, product,...) inner_product() Combines all elements of two ranges adjacent_difference() Combines each element with its predecessor; converts absolute values to relative values partial_sum() Combines each element with all of its predecessors; converts relative values to absolute values
50
vidle ◦ vstup: vektor čísel ◦ výstup: multiset čísel větších než X inkrementovaný o Y filmová databáze ◦ název filmu, režisér, rok,... ◦ vypsat setříděné podle roku a názvu filmu podle režiséra a roku jednoduchý makroprocesor ◦ vstup: text #novemakro obsah makra # dalsi novemakro konec ◦ vystup: text dalsi obsah makra konec ◦ vstup: tt #m1 xx # #m2 yy m1 # pozor - v definici makra může být obsažena hodnota jiného makra
51
Zadání ◦ kontejner obsahující hodnoty libovolného typu ◦ int, double, string, complex, zlomky,... Technické upřesnění ◦ třída Seznam, operace add, print ◦ společný předek prvků AbstractVal ◦ konkrétní prvky IntVal, StringVal,... ◦ stačí jednoduchá implementace vektorem ◦ pole objektů vs. pole 'odkazů' IVIV x AVAV IVIV x AVAV SV s AVAV
52
class AbstractVal { public: virtual void print() = 0; }; typedef ??? valptr; typ odkazu class Seznam { public: void add( valptr p ); void print(); private: vector pole; }; int main() { Seznam s; s.add(.... 123 ); s.add(.... "456" ); s.print(); } abstraktní předek umí existovat a vytisknout se vektor odkazů použití ??? valptr ◦ AbstractVal * ◦ AbstractVal & ◦ unique_ptr ◦ shared_ptr ◦... ?
53
class AbstractVal; typedef unique_ptr valptr; class Seznam { public: void add( valptr p ) { pole.push_back( move( p)); } void print() { for( const auto& x : pole ) x->print(); } private: vector pole; }; int main() { Seznam s; s.add( make_unique (123)); s.add( make_unique ("456")); s.print(); } #include proč '->' ? konstruktory? destruktory?
54
class IntVal : public AbstractVal { public: IntVal( int x) : x_( x) {} virtual void print() { cout << x_; } private: int x_; }; class StringVal : public AbstractVal { public: StringVal( int x) : x_( x) {} virtual void print() { cout << x_; } private: string x_; }; class DoubleVal : public AbstractVal; class ComplexVal : public AbstractVal; class LongintVal : public AbstractVal; class FractionVal : public AbstractVal; what's the difference?
55
int main() { Seznam s1, s2; s1.add( make_unique (123)); s1.add( make_unique ("456")); s2 = s1; s2.print(); } čím je to zajímavé?
56
int main() { Seznam s1, s2; s1.add( make_unique (123)); s1.add( make_unique ("456")); s2 = s1; s2.print(); } čím je to zajímavé? compiler error: XXXX unique_ptr XXX attempting to reference a deleted function class Seznam {.... Seznam( const Seznam& s) = delete; Seznam& operator=(const Seznam& s) = delete; }; možné řešení: zakázat !!! copy constructor a operator= by se měly chovat stejně před C++11: private: operator=
57
Seznam& Seznam::operator=(const Seznam& s) { for( const auto& x : s.pole) pole.push_back( x); return *this; } řešení?
58
Seznam& Seznam::operator=(const Seznam& s) { for( const auto& x : s.pole) pole.push_back( x); return *this; } řešení? compiler error: XXXX unique_ptr XXX attempting to reference a deleted function
59
Seznam& Seznam::operator=(const Seznam& s) { for( const auto& x : s.pole) pole.push_back( make_unique ( *x)); return *this; } řešení? int main() { Seznam s; s.add( make_unique (123)); s.add( make_unique ("456")); s.print(); } motivace nechci kopírovat ukazatel chci vytvořit nový objekt
60
Seznam& Seznam::operator=(const Seznam& s) { for( const auto& x : s.pole) pole.push_back( make_unique ( *x)); return *this; } řešení? jaký typ použít?
61
Seznam& Seznam::operator=(const Seznam& s) { for( const auto& x : s.pole) pole.push_back( make_unique ( *x)); return *this; } řešení? compiler error: cannot instantiate abstract class class AbstractVal { public: virtual void print() = 0; };
62
Seznam& Seznam::operator=(const Seznam& s) { for( const auto& x : s.pole) pole.push_back( make_unique ( *x)); return *this; } řešení?.... 2:30 v noci, ráno je deadline class AbstractVal { public: virtual void print() {} }; tak tu abstraktnost zrušíme!
63
IVIV x AVAV IVIV x AVAV SV s AVAV AVAV AVAVAVAV Je to správně? ◦ Není !! ◦ slicing ◦ pouze část objektu ◦ společný předek horší chyba než předchozí případ ◦ projde kompilátorem ◦ nespadne ◦ dělá nesmysly!
64
Seznam& Seznam::operator=(const Seznam& s) { for( const auto& x : s.pole) { switch( x->get_t()) { case AbstractVal::T_INT: pole.push_back( make_unique ( *x)); break; case AbstractVal::T_STRING: pole.push_back( make_unique ( *x)); break; } return *this; } řešení? class AbstractVal { public: enum T { T_INT, T_DOUBLE,...}; virtual T get_t() const; }; Co s tím? ◦ skutečná hodnota IntVal ⇒ vytvořit IntVal ◦ skutečná hodnota StringVal ⇒ vytvořit StringVal
65
Seznam& Seznam::operator=(const Seznam& s) { for( const auto& x : s.pole) { switch( x->get_t()) { case AbstractVal::T_INT: pole.push_back( make_unique ( *x)); break; case AbstractVal::T_STRING: pole.push_back( make_unique ( *x)); break; } return *this; } FUJ !!! class AbstractVal { public: enum T { T_INT, T_DOUBLE,...}; virtual T get_t() const; }; Co s tím? ◦ skutečná hodnota IntVal ⇒ vytvořit IntVal ◦ skutečná hodnota StringVal ⇒ vytvořit StringVal ošklivé těžko rozšiřitelné zásah do předka
66
Jak to udělat lépe? ◦ využít mechanismus pozdní vazby ◦ každý prvek bude umět naklonovat sám sebe ◦ rozhraní v AbstractVal, implementace v IntVal,... ◦ virtuální klonovací metoda class AbstractVal { public: virtual void print() = 0; virtual valptr clone() = 0; }; class IntVal : public AbstractVal {.... virtual valptr clone() { return make_unique (*this); } };... operator=(const Seznam& s) { for( const auto& x : s.pole) pole.push_back( x->clone()); return *this; } od 0x11: kovariantní návratový typ IVIV x AVAV IVIV x AVAV
67
Copy-constructor a operator= ◦ společné chování ◦ operator= navíc úklid starého stavu, vrací referenci ◦ společné tělo class Seznam { public:.... Seznam() {} Seznam( const Seznam& s ) { clone( s ); } Seznam& operator=(const Seznam& s) { pole.clear(); clone( s ); return *this; } private: void clone( const Seznam& s ) { for( const auto& x : s.pole ) pole.push_back( x->clone() );} vector pole; }; řešení?
68
int main() { Seznam s; s.add( make_unique (123)); s.add( make_unique ("456")); s = s; } čím je to zajímavé? takhle blbě by to asi nikdo nenapsal, ale.... int main() { vector s;.... s[i] = s[j]; }
69
nejdřív si sám celé pole smažu... a potom nakopiruju...... NIC! int main() { vector s;.... s[i] = s[j]; } class Seznam { public:.... Seznam& operator=(const Seznam& s) { pole.clear(); clone( s ); return *this; } }; Seznam& operator=(const Seznam& s) { if( this == &s) return *this; pole.clear(); clone( s ); return *this; }; rovnost ukazatelů ⇒ stejný objekt řešení?
70
Je třeba 'trocha' opatrnosti ◦... a rozumět tomu, co se v programau děje ◦ naimplementujte si sami ◦ = jen dát dohromady předchozí moudra Námět k rozmyšlení ◦ jak by se změnila sémantika (chování), kdybychom použili shared_ptr Verze s raw-pointry ◦ zastaralá ◦ navíc další problémy destruktory, dealokace při nešikovné implementaci odlet! ◦ výhoda unique_ptr: kompilační kontrola ◦ pro zájemce/masochisty na konci slajdů...
71
#include "Scitacka.h" int main() { Scitacka s; s.add( 1); s.add( 2); int x = s.result(); } class Scitacka { public: Scitacka() : val_( 0) {} void add( int x); int result() { return val_; } private: int val_; }; void Scitacka::add( int x) { val += x } int main() { Scitacka s; s.add( 1); s.add( 2); int x = s.result(); } použití instanciace template class Scitacka { public: Scitacka() : val_( 0) {} void add( T x); T result() { return val_; } private: T val_; }; template void Scitacka ::add( T x) { val += x } hlavička i u definice těla hlavička šablony scitacka.h šablona těla musí být při kompilaci viditelná tělo v headeru !
72
TTTT T* make_unique (chunk) vector > Problém ◦ std::vector nezachovává umístění prvků ◦ přidání → invalidace referencí, iterátorů,... Chci: datová struktura zachovávající umístění ◦ žádné invalidace ◦ konstantní časová složitost přístupu k prvkům ◦ [i/chunk][i%chunk] vektor s nehýbatelnými prvky x.push_back(n) x[i] alternativně: automatické rozšíření na x[i] u_p chunk size
73
template class Pole { public: Pole(size_t chunk = 100) : chunk_(chunk), size_(0) {} void push_back(const T& x) { resize( ++size_); (*this)[size_-1] = x; } T& operator[] (size_t i) { return hrabe_[i / chunk_][i%chunk_]; } T& at(size_t i) { check(i); return (*this)[i]; } private: void check(size_t i) { if (i >= size_) throw 1; } void resize(size_t i) { for (size_t k = hrabe_.size(); k <= i / chunk_; ++k) hrabe_.push_back( make_unique (chunk_)); } size_t chunk_; size_t size_; vector > hrabe_; }; new T[chunk_] auto p = make_unique (chunk_)); hrabe_.push_back( move( p));
74
vyvolání výjimky ◦ try blok ◦ nejbližší vyhovující catch blok ◦ stack unwinding destrukce všech objektů dvojitá výjimka ◦ terminate try { if( error) throw exctype; } catch( exctype& e) { e.xxx(); } catch(...) { yyy; } #include class exception { public: exception( ); virtual const char *what( ) const; }; bad_alloc, bad_cast, domain_error, invalid_argument, length_error, out_of_range, overflow_error, range_error, underflow_error } catch( exception& e) { cout << e.what() << endl; } potomci std::exception
75
Výjimky v destruktoru ◦ nikdy! ◦ destruktory se volají při obsluze výjimek Výjimky v konstruktoru ◦ ne globální! není kde chytit ◦ Základní třída konstruktor může vyvolat výjimku ◦ Odvozená třída výjimku inicializace je vhodné zachytit objekt není vytvořen tělo konstruktoru odvozené třídy se neprovede class A { public: A( X& x) {... throw... } }; class B : public A { public: B( X& x) try : A(x) {... } catch(...) { } }; tělo try bloku je tělem konstruktoru exception-safe programming: LS
76
#include, class myexc : public std::exception { public: myexc( int ix) : ix_(ix) { strcpy( buf_, "Chyba na indexu: "); ltoa( ix, buf_+strlen( buf_), 10); } virtual const char *what( ) const { return buf_; } int getIndex() const { return ix_; } private: int ix_; char buf_[100]; }; vše zpracovat v konstruktoru try { nejakymujkod } catch( myexc& me) { cout << "Chyba indexu: " << me.getIndex() << endl; } catch( exception& e) { cout << e.what() << endl; } myclass::myfnc() { whatever(); if( error_occured) throw myexc( 17); whatever_else(); } žádné alokace !!! kompatibilita vlastní diagnostika anonymní instance žádné alokace !
77
Vyzkoušet ◦ nějak velké pole ◦ náhodně zkoušet ◦ při přetečení výjimka Podrobnější diagnostika ◦ vlastní výjimka ◦ špatný index a velikost pole const int max = 100; int n = random( max); vector v; int i; for( i = 0; i < n; i++) v.push_back( i); try { for(;;) { i = random( max); // cout << v[i] << " "; !!! nehazi, odleti cout << v.at(i) << " "; } } catch( exception &e) { cout << endl << e.what() << endl; } catch(...) { cout << "Obscure exception!" << endl; } #include default_random_engine generator( time(0)); uniform_int_distribution distribution(1,6); int n = distribution(generator);
78
C:\> myprog.exe -n -w a.txt b.txt 0 myprog.exe\0 -n -w a.txt b.txt argv 5 argc int main( int argc, char** argv) pole řetězců (ukazatelů na char) Počet parametrů včetně názvu programu ! = počet ukazatelů v argv
79
int main( int argc, char** argv) { int n=0, w=0; while( *++argv && **argv=='-') { switch( argv[0][1]) { case 'n': n = 1; break; case 'w': w = 1; break; default: error(); } if( !argv[0] || !argv[1]) error(); doit( argv[0], argv[1], n, w); return 0; } options usage: myprog [-n] [-w] fileA fileB nastavení přepínače zbývající parametry výkonná funkce prg.exe\0 -n -w a.txt b.txt 0 argv
80
čtení ze souboru i std vstupu záměnnost ◦ std vstup i soubor jsou streamy ◦ lze přiřadit za běhu #include ifstream x; x.open( "file.txt"); if( ! x.good()) { "chyba" } for (;;) { x >> a; if( x.fail()) break; f( a); } x.close(); lepší než test na eof istream *in; in = & cin; if(...) { in = new ifstream(...); } *in << "cokoliv"; není třeba psát zvláštní kód pro čtení souboru stav streamu výsledek předchozí operace
81
přetížení operátoru << není to metoda třídy ale friend globální funkce ◦ nemáme přístup do implementace ostream class Complex { public: Complex() : re_(0), im_(0) {} friend ostream& operator<< ( ostream& out, const Complex& x); private: double re_, im_; }; ostream& operator<< ( ostream& out, const Complex& x) { out << "[" << x.re_ << "," << x.im_ << "]" << endl; return out; } toto není metoda
82
string s1, s2; int i1, i2; f >> s1 >> i1 >> s2 >> i2; if( f.fail())...;... stream zůstává za posledním čtením ws (mezery,...) se automaticky přeskočí
83
string s; for( ;;) { getline( f, s); if( f.fail()) break; cout << "[" << s << "]" << endl; } const int MaxBuf = 4095; char buffer[ MaxBuf+1]; for( ;;) { f.getline( buffer, MaxBuf); if( f.fail()) break; cout << "[" << buffer << "]" << endl; } vždy limit string r, s1, s2; for( ;;) { getline( f, r); if( f.fail()) break; stringstream radek(r); radek >> s1 >> s2; cout << "[" << s1 << s2 << "]" << endl; } parsování řádku
84
string s; string::iterator b, e; char delim = ';'; while( getline( f, s)) { b = e = s.begin(); while( e != s.end()) { e = find( b, s.end(), delim); string val( b, e); cout << "[" << val << "]"; b = e; if( e != s.end()) b++; } cout << endl; } přeskočí oddělovač dokud přečtené slovo není na konci vrátí iterator na odělovač přečte hodnotu mezi oddělovači
85
f >> ws; if( isdigit( f.peek())) { int i; f >> i; cout << "[" << i << "]" << endl; } else { string s; f >> s; cout << "{" << s << "}" << endl; } přečte se nejbližší znak, ale nechá se ve streamu
86
endlvloží nový řádek setw(val)nastaví šířku výstupu setfill(c)nastaví výplňový znak dec, hex, octčte a vypisuje v dané soustavě left, rightzarovnávání fixed, scientificformát výpisu čísla precision(val)nastaví přesnost wspřeskočí bílé znaky (no)skipwsnastavení/zrušení přeskakování bílých znaků při čtení (no)showpointnastaví/zruší výpis desetinné čárky...
87
speciální funkce ◦ předávané ukazatelem ◦ vrací referenci na modifikovaný stream jak to funguje přesněji: šablona cout << 1 << mriz << 2 << mriz << 3 << endl; ostream& mriz( ostream& io) { io << " ### "; return io; } ostream& operator<< (ostream& (* pf)(ostream&)); přetížená matoda na ukazatel na funkci ukazatel na funkci funkce zavolá ji op<<
88
nelze předdefinovaná funkce ◦ libovolné možné parametry ošklivé řešení ◦ vlastní funkce s extra parametrem hezčí řešení ◦ zvláštní třída, zvláštní přetížení << cout << 1 << mriz(5) << 2 << mriz(3) << 3 << endl; cout << mriz(cout,5) <<...
89
vlastní třída ◦ anonymní instance ◦ parametr konstruktoru ◦ přetížení << na tuto třídu class tecka { private: int n_; public: explicit tecka( int n) : n_( n) {} int get_n() const { return n_; } }; ostream& operator<<( ostream& io, const tecka & p) { int n = p.get_n(); while( n--) io << "."; return io; } cout << 1 << tecka(5) << 2 << tecka(3) << 3 << endl; jiná instance zřetězení << příkládek: rzlomek z( 3, 4); cout << z; známý trik: separace inicializace a volání 3 / IV
90
class A { public: virtual int f(){ return 1; }; int g(){ return 2; }; int h(){ return f(); }; virtual int j(){ return g(); }; }; class B : public A { public: virtual int f(){ return 3; }; int g(){ return 4; }; int h(){ return g(); }; virtual int j(){ return f(); }; }; A x; B y; A * p = &x; A * q = &y; B * r = &y; xy.fghj(); pqr -> fghj();
91
int x = 1; class A { public: A(){ x += 1; } virtual ~A(){ x += 2; } }; class B : public A { public: B(){ x *= 2; } virtual ~B(){ x *= 3; } }; A *p = new B; cout << x; delete p; cout << x;
92
Organizational and Policy Issues 0. Don’t sweat the small stuff. (Or: Know what not to standardize.) 1. Compile cleanly at high warning levels. 2. Use an automated build system. 3. Use a version control system. 4. Invest in code reviews. Design Style 5. Give one entity one cohesive responsibility. 6. Correctness, simplicity, and clarity come first. 7. Know when and how to code for scalability. 8. Don’t optimize prematurely. 9. Don’t pessimize prematurely. 10. Minimize global and shared data. 11. Hide information. 12. Know when and how to code for concurrency. 13. Ensure resources are owned by objects. Use explicit RAII and smart pointers. Coding Style 14. Prefer compile- and link-time errors to run-time errors. 15. Use const proactively. 16. Avoid macros. 17. Avoid magic numbers. 18. Declare variables as locally as possible. 19. Always initialize variables. 20. Avoid long functions. Avoid deep nesting. 21. Avoid initialization dependencies across compilation units. 22. Minimize definitional dependencies. Avoid cyclic dependencies. 23. Make header files self-sufficient. 24. Always write internal #include guards. Never write external #include guards. Sutter, Alexandrescu: C++ 101 programovacích technik (C++ Coding Standards)
93
Functions and Operators 25. Take parameters appropriately by value, (smart) pointer, or reference. 26. Preserve natural semantics for overloaded operators. 27. Prefer the canonical forms of arithmetic and assignment operators. 28. Prefer the canonical form of ++ and --. Prefer calling the prefix forms. 29. Consider overloading to avoid implicit type conversions. 30. Avoid overloading &&, ||, or, (comma). 31. Don’t write code that depends on the order of evaluation of function arguments. Class Design and Inheritance 32. Be clear what kind of class you’re writing. 33. Prefer minimal classes to monolithic classes. 34. Prefer composition to inheritance. 35. Avoid inheriting from classes that were not designed to be base classes. 36. Prefer providing abstract interfaces. 37. Public inheritance is substitutability. Inherit, not to reuse, but to be reused. 38. Practice safe overriding. 39. Consider making virtual functions nonpublic, and public functions nonvirtual. 40. Avoid providing implicit conversions. 41. Make data members private, except in behaviorless aggregates (C-style structs). 42. Don’t give away your internals. 43. Pimpl judiciously. 44. Prefer writing nonmember nonfriend functions. 45. Always provide new and delete together. 46. If you provide any class-specific new, provide all of the standard forms (plain, in-place, and nothrow).
94
Construction, Destruction, and Copying 47. Define and initialize member variables in the same order. 48. Prefer initialization to assignment in constructors. 49. Avoid calling virtual functions in constructors and destructors. 50. Make base class destructors public and virtual, or protected and nonvirtual. 51. Destructors, deallocation, and swap never fail. 52. Copy and destroy consistently. 53. Explicitly enable or disable copying. 54. Avoid slicing. Consider Clone instead of copying in base classes. 55. Prefer the canonical form of assignment. 56. Whenever it makes sense, provide a no-fail swap (and provide it correctly). Namespaces and Modules 57. Keep a type and its nonmember function interface in the same namespace. 58. Keep types and functions in separate namespaces unless they’re specifically intended to work together. 59. Don’t write namespace usings in a header file or before an #include. 60. Avoid allocating and deallocating memory in different modules. 61. Don’t define entities with linkage in a header file. 62. Don’t allow exceptions to propagate across module boundaries. 63. Use sufficiently portable types in a module’s interface. Templates and Genericity 64. Blend static and dynamic polymorphism judiciously. 65. Customize intentionally and explicitly. 66. Don’t specialize function templates. 67. Don’t write unintentionally nongeneric code. Error Handling and Exceptions 68. Assert liberally to document internal assumptions and invariants. 69. Establish a rational error handling policy, and follow it strictly. 70. Distinguish between errors and non-errors. 71. Design and write error-safe code. 72. Prefer to use exceptions to report errors. 73. Throw by value, catch by reference. 74. Report, handle, and translate errors appropriately. 75. Avoid exception specifications.
95
STL: Containers 76. Use vector by default. Otherwise, choose an appropriate container. 77. Use vector and string instead of arrays. 78. Use vector (and string::c_str) to exchange data with non-C++ APIs. 79. Store only values and smart pointers in containers. 80. Prefer push_back to other ways of expanding a sequence. 81. Prefer range operations to single-element operations. 82. Use the accepted idioms to really shrink capacity and really erase elements. STL: Algorithms 83. Use a checked STL implementation. 84. Prefer algorithm calls to handwritten loops. 85. Use the right STL search algorithm. 86. Use the right STL sort algorithm. 87. Make predicates pure functions. 88. Prefer function objects over functions as algorithm and comparer arguments. 89. Write function objects correctly. Type Safety 90. Avoid type switching; prefer polymorphism. 91. Rely on types, not on representations. 92. Avoid using reinterpret_cast. 93. Avoid using static_cast on pointers. 94. Avoid casting away const. 95. Don’t use C-style casts. 96. Don’t memcpy or memcmp non-PODs. 97. Don’t use unions to reinterpret representation. 98. Don’t use varargs (ellipsis). 99. Don’t use invalid objects. Don’t use unsafe functions. 100. Don’t treat arrays polymorphically.
96
Zadání: ◦ kontejner obsahující čísla libovolného typu int, double, string, complex,... Technické upřesnění: ◦ třída Seznam ◦ operace append, print ◦ společný předek prvků AbstractNum ◦ konkrétní prvky IntNum, DoubleNum,... ◦ stačí jednoduchá implementace polem ◦ pole objektů vs. pole odkazů IN x AN DN d AN IN x AN S
97
class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; int main(int argc, char** argv){ Seznam s; s.append( new.... ); s.print(); return 0; } class AbstractNum { public: virtual void print()=0; virtual ~AbstractNum() {} }; abstraktní předek umí existovat a vytisknout se virtuální destruktor! přidávání dynamicky vytvořených konkrétních typů
98
class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; // konstruktor Seznam::Seznam() { for(int i=0; i<MAX; ++i) pole[i]=0; n=0; } // destruktor Seznam::~Seznam() { for(int i=0; i<n; ++i) delete pole[i]; n=0; } // tisk seznamu void Seznam::print() { for(int i=0; i<n; ++i) pole[i]->print(); } // pridani prvku do seznamu void Seznam::append(AbstractNum* p) { if (n<MAX) pole[n++]=p; } každý prvek ví jak se vytisknout
99
class IntNum : public AbstractNum { public: IntNum(int x) : x_(x) {} virtual ~IntNum() {} virtual void print() { cout << x_; } private: int x_; }; class IntDouble : public AbstractNum { public: IntDouble(double d) : d_(d) {} virtual ~IntDouble() {} virtual void print() { cout << d_; } private: double d_; }; Seznam s; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); konkrétní datové typy implementují vlastní metody jednotného rozhraní kontejner obsahuje různé typy... a všechny vytiskne konkrétní datové typy implementují vlastní metody jednotného rozhraní
100
class IntNum : public AbstractNum { public: IntNum(int x) { x_ = x; } private: const int x_; }; Požadavek: co když chci zakázat měnit hodnotu prvků compiler error: x must be initialized in constructor base / member initializer list class IntNum : public AbstractNum { public: IntNum(int x) : x_(x) {} private: const int x_; }; seznam inicializátorů používejte všude, kde to lze
101
int main(int argc, char** argv){ Seznam s, s2; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); s2 = s; return 0; } Problém: přiřazení seznamů Je to korektní kód?
102
Problém: přiřazení seznamů Spadne to! Kde? v destruktoru ~Seznam Proč? Navíc: memory leaks int main(int argc, char** argv){ Seznam s, s2; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); s2 = s; return 0; } Tady! problém je v s2 = s ; v Seznam není operator= kompilátor si ho vyrobí automaticky okopíruje datové položky a ukazatele !!! destruktor s2 dealokuje prvky destruktor s znovu odalokuje prvky bloky už ale neexistují !
103
Možné řešení: zakázání přiřazení class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: Seznam& operator=(const Seznam&); enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; operator= v sekci private znemožní přiřazení seznamů stačí pouze deklarace (bez těla) nikdo ho nemůže zavolat je to už teď konečně korektní ??
104
Není! copy konstruktor! class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); private: Seznam& operator=(const Seznam&); Seznam(const Seznam&); enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; je to už teď konečně korektní ?? int main(int argc, char** argv){ Seznam s; s.append(new IntNum(234)); s.append(new DoubleNum(1.45)); s.append(new IntNum(67)); s.print(); Seznam s2 = s; return 0; } oprator= a copy konstruktor by se měly chovat stejně!
105
Pokud chceme dovolit přiřazení (kopírování), je nutné si ujasnit logiku ◦ má se změna projevit i v druhém seznamu? ◦ kopie hodnot nebo kopie datové struktury? ◦ typicky: chování jako kdyby se okopírovaly všechny prvky Každá třída s odkazy na dynamicky alokovaná data ◦ buď zakázat přiřazení operator= a copy konstruktor do sekce private ◦ nebo nadefinovat kopírování napsat vlastní duplikaci VŽDY napsat hlavičku operatoru = a copy konstruktoru! Seznam a, b;.... b = a; a[1]->x = 999;// b[1]->x ???
106
class Seznam { public: void append( AbstractNum *p); void print(); Seznam(); ~Seznam(); Seznam& operator=(const Seznam&); private: enum { MAX = 100 }; AbstractNum* pole[MAX]; int n; }; je to správně ?? Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<s.n; ++i) pole[i] = s.pole[i]; n = s.n; return *this } okopíruji všechny prvky
107
Je to správně? Není !! nezruší se předchozí odkazy! memory leaks je to už teď správně ?? Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<n; ++i) // jako v destruktoru delete pole[i]; for(int i=0; i<s.n; ++i) // jako v copy konstr. pole[i] = s.pole[i]; n = s.n; return *this } nejdříve zruším všechny prvky cílového kontejneru
108
Je to už teď správně? Není !! okopírují se pouze ukazatele data zůstanou stejná prakticky totéž, jako kdybychom nechali automaticky vygenerovaný operator = musíme vygenerovat nové prvky je to už teď správně ?? Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<n; ++i) delete pole[i]; for(int i=0; i<s.n; ++i) pole[i] = new AbstractNum( *s.pole[i]); n = s.n; return *this } dynamická alokace nového prvku přímá konverze odvozené třídy na AbstractNum&
109
Je to už teď správně? Není !! AbstractNum je abstraktní třída nelze instanciovat (vytvořit objekt) neprojde kompilátorem je to už teď správně ?? Seznam& Seznam::operator=( const Seznam& s) { for(int i=0; i<n; ++i) delete pole[i]; for(int i=0; i<s.n; ++i) pole[i] = new AbstractNum( *s.pole[i]); n = s.n; return *this } class AbstractNum { public: virtual void print() {}; virtual ~AbstractNum() {} }; psychicky zdeptaný programátor: tak tu abstraktnost odstraníme!
110
Je to už teď správně? Není !! vytvoří se pouze část objektu - společný předek mnohem horší chyba než předchozí případ projde kompilátorem, nespadne, ale dělá nesmysly! slicing Co s tím? když je skutečná hodnota IntNum - vytvořit IntNum když je skutečná hodnota DoubleNum - vytvořit doubleNum IN x AN class AbstractNum { public: enum T { T_INT, T_DOUBLE,...}; virtual T get_t() const; virtual void print()=0; virtual ~AbstractNum() {} }; switch( s.pole[i]->get_t()) { case AbstraktNum::T_INT: pole[i] = new IntNum(*s.pole[i]); break;... je to už teď správně ??
111
Je to už teď správně? Nooo..... dělá to to, co má, ale... je to ošklivé - těžko rozšiřitelné přidání nového typu vyžaduje zásah do impl. společného předka! navíc syntaktická chyba předka nelze automaticky konvertovat na potomka new IntNum(*s.pole[i]) - skutečný parametr typu AbstractNum& konverze new IntNum(* (IntNum*) s.pole[i]) new IntNum( (IntNum&) *s.pole[i]) Jak to udělat lépe? využít mechanismus pozdní vazby každý prvek bude umět naklonovat sám sebe rozhraní v AbstractNum, implementace v IntNum, DoubleNum,... virtuální klonovací metoda
112
class AbstractNum { public: virtual AbstractNum* clone() const =0; virtual void print()=0; virtual ~AbstractNum() {} }; je to už teď správně ?? class IntNum : public AbstractNum { public: virtual AbstractNum* clone() const { return new IntNum(*this); } IntNum(int x) : x_(x) {} private: const int x_; }; Seznam& Seznam::operator=( const Seznam& s) {... for(int i=0; i<s.n; ++i) pole[i] = s.pole[i]->clone();... jednotné rozhraní na klonovací funkce starší norma: musí být typu AbstractNum* jinak by to nebyla stejná virt. metoda od 0x11: kovariantní návratový typ povolen IntNum AbstractNum případné posunutí ukazatelů řeší automaticky mechanismus virt. metod IN x AN
113
Je to už teď správně? Pořád není !!!! co když někdo provede s = s ? takhle blbě to asi nikdo nenapíše, ale... Seznam p[100]; p[i] = p[j]; nejprve se zruší všechny prvky... a pak se kopírují dealokované bloky!!! ani vynulování ukazatelů moc nepomůže neokopírovalo by se nic nutná ochrana! je to už teď správně ?? Seznam& Seznam::operator=( const Seznam& s) { if( this == &s) return *this;... rovnost ukazatelů stejný objekt this&s
114
Seznam& Seznam::operator=( const Seznam& s) { if( this == &s) return *this;... rovnost ukazatelů stejný objekt Je to už teď správně?.... no teď už snad ano ale co když jsou referencované objekty velké? časté kopírování neefektivní optimalizace zachování sémantiky vs. odlišná implementace reference counting,...
Podobné prezentace
© 2024 SlidePlayer.cz Inc.
All rights reserved.