Ugrás a tartalomhoz

rule of three

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


Főnév

rule of three (tsz. rule of threes)

  1. (informatika) A „Rule of Three” (Hármas szabály) a C++ egyik alapelve, amely az erőforráskezeléshez (főleg dinamikusan foglalt memória, fájlok, mutexek stb.) kapcsolódó osztályok megírásakor játszik kulcsszerepet. A szabály kimondja, hogy ha egy osztálynak szüksége van a destruktor (~ClassName), a másoló konstruktor (ClassName(const ClassName&)) vagy a másoló értékadó operátor (operator=) explicit definiálására, akkor a másik kettőt is definiálni kell. Ez azért szükséges, mert ezek az elemek együtt kezelik a mély másolást (deep copy), és így biztosítják az erőforrások biztonságos és helyes másolását, illetve felszabadítását.



💡 A probléma gyökere: Shallow copy vs. Deep copy

Shallow copy (sekély másolat)

A másolás során csak a mutató kerül átmásolásra, nem maga az adathalmaz. Ez dupla felszabadításhoz (double free) és hibás működéshez vezethet.

class MyClass {
    int* data;
public:
    MyClass(int value) {
        data = new int(value);
    }
    ~MyClass() {
        delete data;
    }
};

Itt, ha példányosítunk két objektumot, majd az egyiket átmásoljuk a másikba, mindkettő ugyanarra a memóriaterületre mutat. Az egyik objektum destruktora törli az erőforrást, a második pedig ismét megpróbálja törölni ugyanazt, ami memóriahibához vezet.



🔁 A három elem

1. Másoló konstruktor

Akkor hívódik meg, amikor egy új objektumot egy már létező példány másolatával hozunk létre.

MyClass a(10);
MyClass b = a; // másoló konstruktor

Ha a másoló konstruktor nincs definiálva, a fordító alapértelmezettet generál, ami sekély másolatot készít.



2. Másoló értékadó operátor

Akkor hívódik meg, amikor egy már létező objektumot másik objektummal akarunk felülírni.

MyClass a(10);
MyClass b(20);
b = a; // értékadó operátor

Ez szintén sekély másolatot végez, ha nem írjuk felül.



3. Destruktor

Akkor hívódik meg, amikor egy objektum elpusztul (például kilép egy blokkhatárból, vagy delete-et hívunk rá). Ha erőforrásokat foglalunk az objektumban (például new-val memóriát), itt kell felszabadítani őket (delete).



✅ Példa a Rule of Three megvalósítására

class MyClass {
    int* data;
public:
    MyClass(int value) {
        data = new int(value);
    }

    // Másoló konstruktor
    MyClass(const MyClass& other) {
        data = new int(*other.data);
    }

    // Másoló értékadó operátor
    MyClass& operator=(const MyClass& other) {
        if (this != &other) { // ön-hozzárendelés ellenőrzése
            delete data; // régi adat felszabadítása
            data = new int(*other.data);
        }
        return *this;
    }

    // Destruktor
    ~MyClass() {
        delete data;
    }

    void print() const {
        std::cout << *data << std::endl;
    }
};

Ezzel biztosítjuk, hogy minden példány saját memóriával rendelkezik, és az erőforrások megfelelően kezelve vannak.



⚠️ Mi történik, ha csak egyet definiálunk?

  • Ha csak destruktort írunk: a másolás sekély marad, ami memóriahibákhoz vezet.
  • Ha csak másoló konstruktort írunk: az értékadó operátor nem fog megfelelően működni.
  • Ha csak értékadó operátort írunk: a másoló konstruktor hibás lehet, különösen ha konténerben használjuk az osztályt (például std::vector<MyClass>).



📦 Modern alternatívák: Rule of Five, Rule of Zero

Rule of Five

A C++11 óta, ha egy osztály erőforrásokat kezel, nemcsak a három fenti tagfüggvény, hanem a mozgató konstruktor és a mozgató értékadó operátor is fontos lehet:

  • Mozgató konstruktor: ClassName(ClassName&&)
  • Mozgató értékadó: ClassName& operator=(ClassName&&)

Ez a Rule of Five (Ötös szabály): ha egyet explicit definiálsz, az összes többit is definiáld.

Rule of Zero

Ha modern C++ eszközöket használsz, mint az std::vector, std::string, std::unique_ptr, akkor nincs szükség saját destruktorra, másolóra, stb. Ez a Rule of Zero: bízd a kezelést az RAII-alapú eszközökre.

class MyBetterClass {
    std::unique_ptr<int> data;
public:
    MyBetterClass(int value) : data(std::make_unique<int>(value)) {}
    void print() const {
        std::cout << *data << std::endl;
    }
};

Itt nincs szükség a három tagfüggvényre – az std::unique_ptr mindent elintéz.



🔁 Összefoglalás

Fogalom Mikor hívódik meg? Feladat
Destruktor Objektum megsemmisülésekor Erőforrás felszabadítása
Másoló konstruktor Új példány létrehozása másolatként Mély másolat készítése
Másoló operátor Létező példány felülírásakor Régi adat törlése, új másolása



🎯 Mikor használd a Rule of Three-t?

  • Ha nyers erőforrást (new, malloc, fájlkezelés, stb.) kezelsz.
  • Ha a másolatkészítés nem triviális.
  • Ha biztosítani akarod az objektum invariánsainak megtartását másoláskor is.