deep copy
Főnév
deep copy (tsz. deep copies)
A mély másolás (Deep Copy) egy olyan módszer C++-ban, amely lehetővé teszi, hogy egy objektum önálló másolatot hozzon létre, külön memóriaterülettel. Ez megakadályozza a sekély másolás által okozott problémákat, például a mutató megosztást, double delete hibát és a dangling pointereket.
1. Mi az a mély másolás?
Definíció:
A mély másolás során minden adattag (beleértve a dinamikusan foglalt memóriát is) önálló másolatként kerül létrehozásra.
Ez azt jelenti, hogy ha egy objektumot másolunk, akkor nem osztozik az eredeti objektummal ugyanazon a memória címen.
Példa sekély másolás és mély másolás különbségére
| Másolás típusa | Mi történik? |
|---|---|
| Sekély másolás (shallow copy) | Az új objektum ugyanarra a memóriacímre mutat, mint az eredeti (veszélyes!) |
| Mély másolás (deep copy) | Az új objektum teljesen különálló példány lesz, saját memóriaterülettel |
2. Miért veszélyes a sekély másolás?
Ha egy osztály dinamikus memóriát (new) használ, a sekély másolás ugyanarra a címre mutató mutatókat hoz létre, ami memóriakezelési hibákhoz vezethet.
Probléma: Sekély másolás mutatókkal
#include <iostream>
using namespace std;
class Auto {
public:
string* marka;
Auto(string m) {
marka = new string(m); // Dinamikus memóriafoglalás
}
~Auto() {
delete marka; // Memória felszabadítása
}
};
int main() {
Auto a1("BMW");
Auto a2 = a1; // Alapértelmezett másolás (sekély másolás!)
cout << *a1.marka << endl; // OK
cout << *a2.marka << endl; // OK, de veszélyes
return 0; // Mindkét objektum destruktora hívódik → double delete hiba!
}
Probléma: 1. Sekély másolás miatt a1.marka és a2.marka ugyanarra a memóriaterületre mutat. 2. Ha az egyik objektum destruktora lefut, felszabadítja a memóriát. 3. A másik objektum még mindig ugyanarra a felszabadított memóriaterületre mutat (dangling pointer hiba). 4. Amikor a második destruktor is lefut, megpróbálja újra felszabadítani ugyanazt a memóriát, ami double delete hibát okoz.
3. Megoldás: Mély másolás
A mély másolás biztosítja, hogy minden objektum saját memóriát használjon.
Mély másolás megvalósítása másoló konstruktorral:
#include <iostream>
using namespace std;
class Auto {
public:
string* marka;
Auto(string m) {
marka = new string(m); // Dinamikus memóriafoglalás
}
// Másoló konstruktor (mély másolás)
Auto(const Auto& masik) {
marka = new string(*masik.marka); // Új memóriafoglalás és másolás
}
~Auto() {
delete marka; // Memória felszabadítása
}
};
int main() {
Auto a1("Mercedes");
Auto a2 = a1; // Most mély másolás történik!
cout << *a1.marka << endl; // "Mercedes"
cout << *a2.marka << endl; // "Mercedes"
return 0;
}
Mi történik itt? - Az Auto(const Auto& masik) másoló konstruktor új memóriát foglal és önálló másolatot készít. - a1.marka és a2.marka külön memóriaterületen van, így nincs megosztott mutató. - A destruktor helyesen tudja felszabadítani a memóriát mindkét objektumnál.
4. Értékadó operátor (operator=) túlterhelése mély másoláshoz
Ha az osztályunk értelmezett értékadás (=) operátort használ, akkor az szintén sekély másolást végez, és újabb problémákat okozhat.
Megoldás: operator= felüldefiniálása mély másolással
#include <iostream>
using namespace std;
class Auto {
public:
string* marka;
Auto(string m) {
marka = new string(m);
}
// Másoló konstruktor
Auto(const Auto& masik) {
marka = new string(*masik.marka);
}
// Értékadó operátor túlterhelése (mély másolás)
Auto& operator=(const Auto& masik) {
if (this != &masik) { // Önvisszaadás ellenőrzése
delete marka; // Régi memória felszabadítása
marka = new string(*masik.marka);
}
return *this;
}
~Auto() {
delete marka;
}
};
int main() {
Auto a1("Tesla");
Auto a2("Ford");
a2 = a1; // Mély másolás történik
cout << *a1.marka << endl; // "Tesla"
cout << *a2.marka << endl; // "Tesla"
return 0;
}
Mi történik itt?
- Ha egy objektum másikat kap értékül (
a2 = a1), akkor azoperator=:- Felszabadítja a régi memóriát (
delete marka). - Új memóriát foglal és lemásolja az adatokat.
- Önvisszaadás ellenőrzése (
if (this != &masik)) megakadályozza az önmagának való értékadást (a = a).
- Felszabadítja a régi memóriát (
5. Mozgató konstruktor (Move Constructor) és mély másolás
C++11-től a másolás helyett mozgatást (move) is használhatunk a hatékonyabb memória-kezelés érdekében.
Mozgató konstruktor a hatékonyságért
class Auto {
public:
string* marka;
Auto(string m) {
marka = new string(m);
}
// Mozgató konstruktor
Auto(Auto&& masik) noexcept {
marka = masik.marka; // Átvesszük a mutatót
masik.marka = nullptr; // Az eredeti objektum érvénytelen lesz
}
~Auto() {
delete marka;
}
};
- Ez a konstruktor átadja a mutatót az új objektumnak, ahelyett, hogy lemásolná.
- Az eredeti objektum
nullptr-re állítja a mutatót, így nincs double delete hiba.
6. Összegzés
| Másolási típus | Mi történik? | Probléma |
|---|---|---|
| Sekély másolás | Ugyanarra a memóriacímre mutató másolat készül | Mutató megosztás, double delete, dangling pointer |
| Mély másolás | Új memóriaterület jön létre minden objektumnál | Több memóriahasználat, de biztonságos |
| Mozgató konstruktor | Az erőforrásokat áthelyezzük, nem másoljuk | Hatékony, de csak C++11-től elérhető |
Konklúzió
A mély másolás elengedhetetlen, ha egy osztály dinamikus memóriát használ. Az alapértelmezett sekély másolás veszélyes lehet, ezért mindig implementáljuk a másoló konstruktort és az értékadó operátort. C++11-től a mozgató konstruktorok is egy hatékony alternatívát kínálnak.