Ugrás a tartalomhoz

deep copy

A Wikiszótárból, a nyitott szótárból


Főnév

deep copy (tsz. deep copies)

  1. (informatika) teljes körű másolat

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 az operator=:
    1. Felszabadítja a régi memóriát (delete marka).
    2. Új memóriát foglal és lemásolja az adatokat.
    3. Önvisszaadás ellenőrzése (if (this != &masik)) megakadályozza az önmagának való értékadást (a = a).



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.