Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 1/28 Mělká a hluboká kopie Ing. Josef Vogel, CSc Katedra softwarového inženýrství Katedra teoretické informatiky, Fakulta informačních technologii, ČVUT v Praze Karel Müller, Josef Vogel, 2011 Programování a algoritmizace 2, BI-PA2, 2011, Přednáška 5 BI-PA2 Evropský sociální fond Praha & EU: Investujeme do vaší budoucnosti
Mělká a hluboká kopie, počítané reference třída Pole s přetíženým operátorem indexování problémy s přiřazením mezi objekty typu Pole (mělká kopie) hluboká kopie pomocí přetíženého operátoru přiřazení kopírující konstruktor třída Pole s použitím techniky počítaných referencí Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 2/28
p5\pole1\main.cpp Dynamické pole Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 3/28 Napíšeme třídu pro realizaci dynamického pole (počet prvků bude dán parametrem konstruktoru), ve které přístup k prvkům bude umožněn operátorem indexování Příklad použití: #include "pole.h" počet prvků pole (může být dán proměnnou) int main() { Pole a(5); for (int i=0; i<a.delka(); i++) a[i] = i; cout << "pole a: " << a << endl; // [0 1 2 3 4] int soucet = 0; for (int i=0; i<a.delka(); i++) soucet += a[i]; cout << "soucet prvku pole je " << soucet << endl; system("pause"); return 0; přetížený operátor indexování
p5\pole1\pole.h Deklarace třídy Pole Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 4/28 typedef int TypPrvku; class Pole { TypPrvku *pole; int pocetprvku; public: explicit Pole(int pp); ~Pole(); int delka() const {return pocetprvku; TypPrvku& operator[](int); const TypPrvku& operator[](int) const; přetížení operátoru indexování pro proměnný objekt typu Pole friend ostream& operator<<(ostream&, const Pole&); ; přetížení operátoru indexování pro konstantní objekt typu Pole
p5\pole1\pole.cpp Definice metod třídy Pole Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 5/28 Pole::Pole(int pp) { pocetprvku = pp; pole = new TypPrvku[pocetPrvku]; for (int i=0; i<pocetprvku; i++) pole[i] = TypPrvku(); Pole::~Pole() { delete [] pole; pole = NULL; pocetprvku = 0;
p5\pole1\pole.cpp Definice metod třídy Pole Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 6/28 TypPrvku& Pole::operator[](int ind) { if (ind<0 ind>=pocetprvku) throw "nedovoleny index"; return pole[ind]; const TypPrvku& Pole::operator[](int ind) const { if (ind<0 ind>=pocetprvku) throw "nedovoleny index"; return pole[ind]; ostream& operator<<(ostream& o, const Pole& p) { for (int i=0; i<p.pocetprvku; i++) o << p.pole[i] << " "; return o;
p5\pole1\main2.cpp Přiřazení mezi objekty typu Pole Podívejme se na funkci main2 int main() { Pole a(5); for (int i=0; i<a.delka(); i++) a[i] = i; cout << "pole a: " << a << endl; // [0 1 2 3 4] Pole b(3); b = a; b[1] = 10; cout << "pole a: " << a << endl; // [0 10 2 3 4] cout << "pole b: " << b << endl; // [0 10 2 3 4] system("pause"); return 0; Proč se změní též a[1], když jsme přiřazovacím příkazem změnili b[1]? Odpověď na dalším slajdu Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 7/28
Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 8/28 Přiřazení mezi objekty typu Pole mělká kopie a 5 0 1 2 3 4 b = a; a 5 0 1 2 3 4 b 5 b[1] = 10; a 5 0 10 2 3 4 b 5
Přiřazení mezi objekty typu Pole mělká kopie Přiřazení mezi objekty stejného typu (které vygeneruje překladač) probíhá tak, že se zkopíruje bitový obsah položek To pro položky typu ukazatel na dynamicky alokovaná data znamená, že oba objekty ukazují na stejná, dynamicky alokovaná data Tento způsob přiřazení mezi objekty obsahujícími ukazatele na dynamicky alokovaná data se nazývá mělká kopie Mělká kopie by nevadila, když o ní víme Problém je s destruktorem Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 9/28
p5\pole2\pole.cpp Přiřazení mezi objekty typu Pole mělká kopie Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 10/28 Upravme destruktor třídy Pole tak, aby vypsal adresu pole, které dealokuje Pole::~Pole() { cout << "dealokace pole na adrese " << (void*)pole << endl; delete [] pole; pole = NULL; pocetprvku = 0;
p5\pole2\main.cpp Přiřazení mezi objekty typu Pole mělká kopie Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 11/28 A použijme v této hlavní funkci: void funkce() { Pole a(5); for (int i=0; i<a.delka(); i++) a[i] = i; cout << "pole a: " << a << endl; // [0 1 2 3 4] Pole b(3); b = a; b[1] = 10; cout << "pole a: " << a << endl; // [0 10 2 3 4] cout << "pole b: " << b << endl; // [0 10 2 3 4] /* zde vypise napr. dealokace pole na adrese 0x3d3e90 dealokace pole na adrese 0x3d3e90 */ int main() { funkce(); system("pause"); return 0; to je špatně, protože nelze vícekrát dealokovat paměť se stejnou adresou
Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 12/28 Přiřazení mezi objekty typu Pole hluboká kopie Při přiřazení x = y mezi objekty typu Pole musíme nejprve dealokovat pole, na které ukazuje objekt x, a pak dynamicky vytvořit kopii pole, na které ukazuje objekt y a b 5 3 0 1 2 3 4 0 0 0 b = a; a 5 0 1 2 3 4 b 5 0 1 2 3 4 dynamická kopie 0 0 0 dealokováno
p5\pole3\pole.h, pole.cpp Třída Pole s přetíženým operátorem = Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 13/28 class Pole { TypPrvku *pole; int pocetprvku; public: explicit Pole(int pp); ~Pole(); int delka() const {return pocetprvku; TypPrvku& operator[](int); const TypPrvku& operator[](int) const; Pole& operator=(const Pole&); friend ostream& operator<<(ostream&, const Pole&); ; Pole& Pole::operator=(const Pole& p) { if (&p==this) return *this; delete [] pole; pocetprvku = p.pocetprvku; pole = new TypPrvku[pocetPrvku]; for (int i=0; i<pocetprvku; i++) pole[i] = p.pole[i]; return *this;
p5\pole3\main.cpp Třída Pole s přetíženým operátorem = Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 14/28 Použití: #include "pole.h" int main() { Pole a(5); for (int i=0; i<a.delka(); i++) a[i] = i; cout << "pole a: " << a << endl; // [0 1 2 3 4] Pole b(3); b = a; b[1] = 10; cout << "pole a: " << a << endl; // [0 1 2 3 4] cout << "pole b: " << b << endl; // [0 10 2 3 4] system("pause"); return 0;
p5\pole3\main2.cpp Třída Pole s přetíženým operátorem = Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 15/28 Podívejme se ještě na další příklad funkce main #include "pole.h" int main() { Pole a(5); toto není přiřazení, ale deklarace objektu doplněná inicializací jiným objektem for (int i=0; i<a.delka(); i++) a[i] = i; cout << "pole a: " << a << endl; // [0 1 2 3 4] Pole b = a; b[1] = 10; cout << "pole a: " << a << endl; // [0 10 2 3 4] cout << "pole b: " << b << endl; // [0 10 2 3 4] system("pause"); return 0; a je to tu zas!
Kopírující konstruktor Deklarací Pole b = a; se vytvoří objekt b a inicializuje se kopíí objektu a Tuto deklaraci a inicializaci převede překladač na deklaraci Pole b(a); ve které se volá tzv. kopírující konstruktor Kopírující konstruktor pro třídu T je konstruktor s jedním parametrem typu const T&, pro třídu Pole má tedy deklaraci Pole(const Pole&); Pokud kopírující konstruktor ve třídě deklarován není, překladač ho vytvoří s tím, že provede mělkou kopii Pokud mělká kopie nám nevyhovuje, musíme kopírující konstruktor deklarovat a definovat sami Poznámka: kopírující konstruktor se volá i při předání objektu jako parametru (pokud parametr není typu reference) Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 16/28
p5\pole4\pole.h, pole.cpp Třída Pole s kopírujícím konstruktorem Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 17/28 class Pole { TypPrvku *pole; int pocetprvku; public: explicit Pole(int pp); Pole(const Pole&); ~Pole(); int delka() const {return pocetprvku; TypPrvku& operator[](int); const TypPrvku& operator[](int) const; Pole& operator=(const Pole&); friend ostream& operator<<(ostream&, const Pole&); ; Pole::Pole(const Pole& p) { pocetprvku = p.pocetprvku; pole = new TypPrvku[pocetPrvku]; for (int i=0; i<pocetprvku; i++) pole[i] = p.pole[i];
Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 18/28 Počítané reference Sdílení dynamicky alokovaných dat lze vyřešit také technikou počítaných referencí. Počítané reference pro třídu Pole kopírující konstruktor Pole a(5); Pole CRPole a... Pole b=a; citacref 5 1 a b 5 2 objekt b se připojí na dynamicky alokovaná data, na které je připojen objekt a
Počítané reference Počítané reference pro třídu Pole přiřazení Pole a(5), b(3); a b b = a; 5 1 3 1 a 5 2 vrátí se do volné paměti b nejprve odpojení, pak připojení 0 Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 19/28
Počítané reference Počítané reference pro třídu Pole přiřazení prvku pole a 5 2 b b[1] = 10; a 5 2 b cout << a[1]; // vypíše 10 dynamicky alokovaná data jsou sdílena Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 20/28
p5\pole5\pole.h Počítané reference Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 21/28 typedef int TypPrvku; class Pole { public: explicit Pole(int pp); Pole(const Pole&); ~Pole(); int delka() const {return ptr->pocetprvku; TypPrvku& operator[](int); const TypPrvku& operator[](int) const; Pole& operator=(const Pole&); friend std::ostream& operator<< (std::ostream&, const Pole&); private:...
p5\pole5\pole.h Počítané reference Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 22/28 private: struct CRPole { TypPrvku *pole; int pocetprvku; int citacref; CRPole(int pp); ~CRPole(); ; CRPole *ptr; void pripoj(crpole *p); void odpoj(); ;
p5\pole5\pole.cpp Počítané reference Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 23/28 Pole::CRPole::CRPole(int d) { pole = new TypPrvku[d]; pocetprvku = d; citacref = 1; for (int i=0; i<pocetprvku; i++) pole[i] = TypPrvku(); Pole::CRPole::~CRPole() { delete [] pole;
p5\pole5\pole.cpp Počítané reference Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 24/28 TypPrvku& Pole::operator[](int ind) { if (ind<0 ind>=ptr->pocetprvku) throw "nedovoleny index"; return ptr->pole[ind]; const TypPrvku& Pole::operator[](int ind) const { if (ind<0 ind>=ptr->pocetprvku) throw "nedovoleny index"; return ptr->pole[ind]; ostream& operator<<(ostream& o, const Pole& p) { int n = p.delka(); for (int i=0; i<n; i++) o << p.ptr->pole[i] << ' '; o << endl; return o;
p5\pole5\pole.cpp Počítané reference Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 25/28 Pole::Pole(int pp) { ptr = new CRPole(pp); Pole::Pole(const Pole& p) { pripoj(p.ptr); Pole::~Pole() { odpoj(); Pole& Pole::operator=(const Pole& p) { if (this==&p) return *this; odpoj(); pripoj(p.ptr); return *this;
p5\pole5\pole.cpp Počítané reference Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 26/28 void Pole::pripoj(Pole::CRPole* p) { ptr = p; ++p->citacref; void Pole::odpoj() { if (--ptr->citacref == 0) delete ptr;
p5\pole5\main.cpp Počítané reference Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 27/28 int main() { Pole a(5); for (int i=0; i<a.delka(); i++) a[i] = i; cout << "pole a: " << a << endl; // [0 1 2 3 4] Pole b = a; b[1] = 10; cout << "pole a: " << a << endl; // [0 10 2 3 4] cout << "pole b: " << b << endl; // [0 10 2 3 4] system("pause"); return 0;
Zákaz kopírujícího konstruktoru a přiřazení Pokud pro instance třídy není třeba kopírující konstruktor a přiřazení, lze jejich použití zakázat Jak? Kopírující konstruktor a operátor přiřazení deklarujeme jako private (pak je nemusíme definovat) Karel Müller, Josef Vogel (ČVUT FIT) Mělká a hluboká kopie BI-PA2, 2011, Přednáška 5 28/28