Sobes.tech
Middle+

Miért fog rosszul működni a vektor, ha örökölni belőle?

sobes.tech MI

Válasz az MI-től

Az std::vector öröklése nem ajánlott, mivel hiányoznak belőle virtuális destruktorok és más virtuális függvények, ami sérti a Liskov-féle helyettesítési elvet (LSP), és problémákat okoz polimorfikus használat során.

Problémák példái:

  1. Szeletelési (slicing) probléma: Amikor az alosztály objektumát érték vagy referencia útján adjuk át az std::vector-nak, az alosztályra jellemző adatok és viselkedés elveszik.

    #include <vector>
    #include <iostream>
    
    class MyVector : public std::vector<int> {
    public:
        int my_data = 100;
        // Nincs virtuális destruktor
        ~MyVector() {
            std::cout << "MyVector destruktor" << std::endl;
        }
    };
    
    void process_vector(std::vector<int> vec) {
        // Érték szerint kapva - szeletelés történik
        // A MyVector destruktor nem lesz meghívva
        std::cout << "std::vector feldolgozása" << std::endl;
    }
    
    int main() {
        MyVector mv;
        mv.push_back(1);
        process_vector(mv); // Szeletelés történik a MyVector objektumából
        return 0;
    } // Csak a std::vector destruktor hívódik meg
    
  2. Virtuális destruktor hiánya: Ha az alosztály objektumát a bázis osztály pointerével töröljük, az alosztály destruktorát nem hívja meg, ami erőforrás-szivárgáshoz vezethet.

    #include <vector>
    #include <iostream>
    #include <memory> // a unique_ptr-hez
    
    class DerivedVector : public std::vector<int> {
    public:
        int* resource;
        DerivedVector() : std::vector<int>(), resource(new int) {
            std::cout << "DerivedVector konstruktor" << std::endl;
        }
        // Nincs virtuális destruktor
        ~DerivedVector() {
            std::cout << "DerivedVector destruktor" << std::endl;
            delete resource; // Nem biztos, hogy meghívódik
        }
    };
    
    int main() {
        // Törlés bázis osztály pointerével
        std::vector<int>* base_ptr = new DerivedVector();
        // delete base_ptr csak a std::vector destruktorát hívja
        delete base_ptr; // Az alosztály destruktor nem hívódik meg, erőforrás szivárgás
    
        // Példa unique_ptr-tel
        // std::unique_ptr<std::vector<int>> up = std::make_unique<DerivedVector>();
        // Scope végén a unique_ptr meghívja a delete-et
        return 0;
    } // Az erőforrásként lefoglalt memória nem szabadul fel
    
  3. Konstruktorok: A std::vector konstruktorainak (pl. másolási, mozgatási konstruktorok) viselkedése nem feltétlenül felel meg az elvárásoknak, ha a származtatott osztály saját állapotot vagy logikát ad hozzá.

A std::vector-ből való öröklés helyett a legjobb megközelítések:

  • Összetétel: Használd a std::vector-t osztálytagként. Ez lehetővé teszi az interfész és a viselkedés kontrollálását, a std::vector belső használatával.
    #include <vector>
    #include <iostream>
    
    class MyContainer {
    private:
        std::vector<int> data;
        int my_extra_data = 100;
    public:
        void add(int val) {
            data.push_back(val);
        }
        const std::vector<int>& get_data() const { // hozzáférés a vektorhoz
            return data;
        }
        // A MyContainer destruktor helyesen hívja a data destruktorát
        ~MyContainer() {
             std::cout << "MyContainer destruktor" << std::endl;
        }
    };
    
    int main() {
        MyContainer mc;
        mc.add(1);
        std::cout << "Adatok mérete: " << mc.get_data().size() << std::endl;
        return 0;
    } // A MyContainer destruktor hívódik, ami meghívja a std::vector destruktorát
    
  • Szabad függvények és algoritmusok: Bővítsd a std::vector funkcionalitását szokásos függvényekkel vagy használd a standard algoritmusokat.

Ezek a megközelítések rugalmasabbak, biztonságosabbak és megfelelnek az OOP és a könyvtártervezés elveinek C++-ban. A standard konténerek nem arra készültek, hogy alap osztályként használják őket.