Sobes.tech
Middle+

Proč bude vektor špatně fungovat, pokud z něj zdědíme?

sobes.tech AI

Odpověď od AI

Dědičnost od std::vector se nedoporučuje kvůli absenci virtuálních destruktorů a dalších virtuálních funkcí, což porušuje princip Liskovovy podměnky (LSP) a způsobuje problémy při polymorfním použití.

Příklady problémů:

  1. Problém s řezáním (slicing): Při předání objektu odvozené třídy hodnotou nebo odkazem na základní třídu std::vector dojde ke ztrátě dat a chování specifického pro odvozenou třídu.

    #include <vector>
    #include <iostream>
    
    class MyVector : public std::vector<int> {
    public:
        int my_data = 100;
        // Bez virtuálního destruktoru
        ~MyVector() {
            std::cout << "Destruktor MyVector" << std::endl;
        }
    };
    
    void process_vector(std::vector<int> vec) {
        // Přijímá hodnotou - dochází ke řezu
        // Destruktor MyVector nebude volán
        std::cout << "Zpracování std::vector" << std::endl;
    }
    
    int main() {
        MyVector mv;
        mv.push_back(1);
        process_vector(mv); // Dochází ke řezu objektu MyVector
        return 0;
    } // Bude volán pouze destruktor std::vector
    
  2. Chybějící virtuální destruktor: Pokud odstraníte objekt odvozené třídy přes ukazatel na základní třídu std::vector, destruktor odvozené třídy nebude volán, což může vést ke ztrátě zdrojů.

    #include <vector>
    #include <iostream>
    #include <memory> // pro unique_ptr
    
    class DerivedVector : public std::vector<int> {
    public:
        int* resource;
        DerivedVector() : std::vector<int>(), resource(new int) {
            std::cout << "Konstruktor DerivedVector" << std::endl;
        }
        // Bez virtuálního destruktoru
        ~DerivedVector() {
            std::cout << "Destruktor DerivedVector" << std::endl;
            delete resource; // Může být nevolán
        }
    };
    
    int main() {
        // Odstranění přes ukazatel na základní třídu
        std::vector<int>* base_ptr = new DerivedVector();
        // Při delete base_ptr se volá pouze destruktor std::vector,
        // destruktor DerivedVector se nevolá, dochází ke ztrátě zdrojů
        delete base_ptr;
    
        // Příklad s unique_ptr
        // std::unique_ptr<std::vector<int>> up = std::make_unique<DerivedVector>();
        // Při ukončení scope zavolá delete na raw ukazateli base_ptr.
        // To je ekvivalentní s delete base_ptr; a způsobí stejný problém, pokud vector nemá virtuální destruktor.
    
        return 0;
    } // Paměť alokovaná pro resource nebude uvolněna
    
  3. Konstruktory: Chování konstruktorů std::vector (například konstruktor kopírování, přesunu) nemusí odpovídat očekáváním pro odvozenou třídu, pokud přidává vlastní stav nebo logiku.

Namísto dědění od std::vector jsou nejlepší přístupy:

  • Kompocice: Použij std::vector jako člena třídy. To umožňuje kontrolovat rozhraní a chování nové třídy, využíváním std::vector uvnitř.
    #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 { // Poskytuje přístup k vektoru
            return data;
        }
        // Destruktor MyContainer správně zavolá destruktor data
        ~MyContainer() {
             std::cout << "Destruktor MyContainer" << std::endl;
        }
    };
    
    int main() {
        MyContainer mc;
        mc.add(1);
        std::cout << "Velikost dat: " << mc.get_data().size() << std::endl;
        return 0;
    } // Destruktor MyContainer je volán, který volá destruktor std::vector
    
  • Volné funkce a algoritmy: Rozšiřte funkčnost std::vector pomocí běžných funkcí nebo standardních algoritmů.

Tyto přístupy jsou flexibilnější, bezpečnější a odpovídají principům OOP a návrhu knihoven v C++. Standardní kontejnery nejsou určeny k použití jako základní třídy.