Klónok, másolás
Másolás dinamikus memóriával¶
Azt már megszoktuk, ha egy egész számot lemásolunk, akkor megkapjuk a ,,tökéletes'' másolatát, azaz a másolat felveszi azt az értéket, amit az eredeti reprezentált.
1 2 3 4 5 |
|
Ezt megtehetjük objektumokkal is (a string
-nél már használtuk és működik). Mivel egy objektumot az egyes adattagok értékei határoznak meg, másoláskor ezeket az értékeket le kell másolnunk. "Egyszerű" esetben ez történik másoláskor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
A fenti példában nem mondtuk meg, hogyan történjen a másolás, ezért a default másolás futott le, mely minden adattagnak az értékét átmásolta. A kimenet emiatt:
6 -> 4
Ez azonban nem megfelelő akkor, ha az objektumban van dinamikusan foglalt memória. Ennek oka, hogy a dinamikusan foglalt memória nem része az objektumnak, csak az a pointer, ami a lefoglalt memóriaterületre mutat. Mivel minden adattag értéke másolásra kerül, a másolt objektum pointere is ugyanarra a területre fog mutatni (hiszen csak a mutatót másoljuk le). Ekkor mind a két objektum ugyanazt a memóriát használja. Ha az egyik módosít rajta, akkor a másiknál is látható ez a változás (hiszen ugyanazt memóriát használják). A másolást viszont azért használjuk, mert meg szeretnénk őrizni az eredeti értéket.
A Kurzus
osztály dinamikus memóriával vizuálisan:
Ha ezt default módon lemásolnánk:
Látszódik, hogy a két külön objektum pointere ugyanarra a heap memória területen lévő elemre mutat. Ha ezt módosítjuk is:
Módosítás után a kurzus2 hallgatói változtak, azonban a kurzus1 hallgatói is megváltoztak, hiszen ugyanaz a kettő.
A helyes másolás ebben az esetben azt jelentené, hogy minden értéket lemásolunk pontosan, de a dinamikusan foglalt memória esetében foglalunk új helyet és minden egyes értéket egyesével átmásolunk. Ekkor a pointer az új területre fog mutatni, ahol ugyan egyező értékek vannak, mégsem ugyan az:
Módosítás esetében ekkor nem ütközünk az előző problémába:
A módosított elem teljesen más memória területen van, így az eredeti értéket nem módosítjuk.
Másoló konstruktor (copy constructor)¶
Ahhoz, hogy dinamikus memóriakezelés esetében megfelelő másolást használjunk, meg kell mondanunk, hogy hogyan történjen a másolás. Ennek egy módja a másoló konstruktor. Copy constructor használatakor egy másik objektum alapján hozzuk létre az új objektumunkat.
Mivel a másoló konstruktor is konstruktor, ennek a neve is megegyezik az osztály nevével, de paraméterében egy ugyanolyan típusú objektumot vár. Mivel a lemásolt objektum nem változik és nem akarunk másolást végrehajtani a paraméter átadásakor, ezért a paramétert konstans referenciaként (const &) adjuk meg.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
A fenti példában a másoló konstruktor kap egy objektumot, és annak az értékeit lemásolja. A dinamikusan foglalt elemek esetében foglal memóriát majd a memóriában tárolt értéket is átmásolja. Ha a new[]
operátorral tömböt foglaltunk volna, akkor minden egyes elemet át kellene másolni, pl. egy for-ciklussal.
Az objektumok hasonlóan viselkednek a primitív típusokhoz, így kérdéses lehet, hogy a másolás elvégezhetjük-e ugyan azzal a szintaxissal mint pl. egy egésznél:
1 2 |
|
Erre van mód:
1 2 |
|
Természetesen az 'explicit' kulcsszó a copy constructorra is hatással van!
Assignment operátor¶
A másoló konstruktor lehetőséget adott arra, hogy egy másik objektum mintájára hozzuk létre az új objektumot. Azonban szükség lehet arra is, hogy egy létező objektumnak szeretnénk értékül adni egy másik objektumot, azaz később adunk értéket egy már létező objektumnak:
1 2 3 4 5 6 |
|
Objektumok esetében is van lehetőségünk megmondani, hogy hogyan történjen meg az értékadás. Ehhez az operator=
-t kell felüldefiniálni. Mivel egy másik objektum alapján szeretnénk beállítani az értékeket:
- ugyanúgy másolás mint a copy konstruktor
- dinamikus memória használatra figyelni kell
- azonos eredményt kell kapjunk mintha copy constructorral másoltunk volna
- de előtte "törölni kell" azt az objektumot, amit éppen aktuálisan már reprezentál
- a kiinduló/másolandó objektumot ebben az esetben sem akarjuk módosítani, tehát a paramétert konstans referenciaként (const &) fogadjuk ebben az esetben is
- az értékadás után egy objektumot kapunk, így a visszatérési érték egy referencia az adott objektumra
Példa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Az assignment operátor használatával így egy másik AssignOp
értékét állítjuk be az adott objektumnak. A foglalt memóriát felszabadítjuk. Ebben az esetben nincsen eltérés, hiszen egy elem mindig egy lesz, azonban ha ez egy változó tömb lenne, a méret változhat, nem biztos, hogy elfér az új érték az eddigi memóriában. Foglalunk új memóriát majd a megfelelő értéket is átmásoljuk.
Ennek a hibája akkor jön elő, ha a következő kifejezést szeretnénk leírni:
1 2 |
|
Ekkor *this
és az ao
is azonos objektum. Az első lépés, hogy az adott objektum (*this
) által használt memóriát (erőforrást) felszabadítjuk, ami az ao
-hoz tartozó memória is, hiszen azonos a kettő. Utána foglalunk új helyet, ez még helyes. Mikor az értéket akarjuk beállítani, akkor az előbb felszabadított memóriából szeretnénk olvasni. Ennek a kivédésére meg kell vizsgálnunk, hogy a két objektum azonos-e, tehát a memória címük ugyanaz-e. Assignment operátor helyesen:
1 2 3 4 5 6 7 8 9 10 |
|
Mivel a copy constructor és az assignment operátor ilyen szoros kapcsolatban állnak, egyszerre kell megvalósítani (vagy letiltani, azaz = delete
) azokat.
postfix ++ operátor¶
A postfix és prefix operator++-ról már volt szó. Működésükben a visszaadott érték esetében van különbség. Postfix esetben a "régi" értéket le kell másolni. Mivel másolatot használunk, dinamikus memóriakezeléskor a postfix ++ operátorhoz kell a copy constructor és az assignment operator.
Példa postfix ++ operátorra:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Objektumok klónozása¶
Az eddigi másolásokban, értékadásokban a közös az volt, hogy mindig tudtuk, hogy pontosan mit, milyen típusú elemet szeretnénk másolni. De mi van akkor, ha a másolás során is szükség lenne arra, hogy azt "polimorfikusan" oldjuk meg? Nyilván ez nem lehetséges, hiszen maguk a konstruktorok nem lehetnek virtuálisak, hiszen nem létezik az objektum, amelyen keresztül meg szeretnénk hívni azt. A megoldás ilyenkor egy olyan metódus biztosítása, ami konkrétan a másolásért felel, de ami virtuálissá tehető, és ilyen esetben ezt alkalmazhatjuk.
A Java nyelv mintájára ennek a metódusnak a neve lehet clone
, amely feladata, hogy az aktuális objektumról egy másolatot készítsen és adjon vissza, és amely a virtual
kulcsszóval polimorfikussá is tehető.
Példaként tekintsük az Egyetem
osztályt, amely szeretné a nyilvántartásában jelen levő alkalmazottakat (illetve a nyilvántartás másolatát) átadni a GazdaságiOsztaly
-nak. Ehhez el kell készítenie az Alkalmazott
akat tároló vector
nak a másolatát. (Biztonsági okokból nem szeretnék, hogy a saját rendszerükbe a GazdaságiOsztaly
bele tudjon nyúlni....). A Kutato
k speciális alkalmazottak, az ő másolásuk nem menne csupán az Alkalmazott
osztály copy konsrtuktorával. Hogy mindezt kipróbáljuk, a következő kódban implementáltuk a gazdasági osztályt, amely az alkalmazottakLekerese
függvénnyel hozzáfér az egyetem alkalmazottaihoz. (Ahhoz, hogy ezt megtehesse, hiszen az Egyetem
osztály ezen adattagja private
, az Egyetem
osztályon belül ezt engedélyezni lehet azzal, hogy ezen függvényt friend
-ként felveszi az Egyetem
osztály.)
Mind az Alkalmazott
, mind a Kutato
osztály rendelkezik copy
konstruktorral. Amennyiben a GazdasagiOsztaly
ezekkel próbálná meg az alkalmazottak másolását (alkalmazottak.push_back(new Alkalmazott(*a));
), Kutato
k esetében is csak azok Alkalmazott
részét másolná át. (Ezt kipróbálhatjuk, ha a másolatoknak meghívjuk a megbeszelesreJar
függvényét.) Ezzel szemben a clone
metódus hívása a megfelelő módon teszi meg a másolást.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
|
Létrehozva: 2018-11-23