Sobes.tech
Middle+

Prečo bude vektor zle fungovať, ak z neho zdedíme?

sobes.tech AI

Odpoveď od AI

Dedičstvo od std::vector sa neodporúča kvôli absencii virtuálnych deštruktorov a iných virtuálnych funkcií, čo narúša LSP (princip náhrady Liskovovej podtriedy) a spôsobuje problémy pri polymorfnom použití.

Príklady problémov:

  1. Problém s reznutím (slicing): Pri odovzdávaní objektu odvodeného triedy hodnotou alebo odkazom na základnú triedu std::vector, budú stratené špecifické údaje a správanie odvodeného triedy.

    #include <vector>
    #include <iostream>
    
    class MyVector : public std::vector<int> {
    public:
        int my_data = 100;
        // Bez virtuálneho deštruktora
        ~MyVector() {
            std::cout << "MyVector deštruktor" << std::endl;
        }
    };
    
    void process_vector(std::vector<int> vec) {
        // Prijíma hodnotou - dochádza k rezu
        // Deštruktor MyVector nebude volaný
        std::cout << "Spracovanie std::vector" << std::endl;
    }
    
    int main() {
        MyVector mv;
        mv.push_back(1);
        process_vector(mv); // Dochádza k rezu objektu MyVector
        return 0;
    } // Tu bude volaný iba deštruktor std::vector
    
  2. Absencia virtuálneho deštruktora: Ak odstránite objekt odvodeného typu cez ukazovateľ na základnú triedu std::vector, deštruktor odvodeného typu nebude volaný, čo môže viesť k únikom zdrojov.

    #include <vector>
    #include <iostream>
    #include <memory> // Pre unique_ptr
    
    class DerivedVector : public std::vector<int> {
    public:
        int* resource;
        DerivedVector() : std::vector<int>(), resource(new int) {
            std::cout << "DerivedVector konštruktor" << std::endl;
        }
        // Chýba virtuálny deštruktor
        ~DerivedVector() {
            std::cout << "DerivedVector deštruktor" << std::endl;
            delete resource; // Nemusí byť volané
        }
    };
    
    int main() {
        // Odstránenie cez ukazovateľ na základný typ
        std::vector<int>* base_ptr = new DerivedVector();
        // Pri delete base_ptr sa volá iba deštruktor std::vector,
        // deštruktor DerivedVector sa nevolá, dochádza k úniku resource
        delete base_ptr;
    
        // Príklad s unique_ptr
        // std::unique_ptr<std::vector<int>> up = std::make_unique<DerivedVector>();
        // up->push_back(5);
        // Pri ukončení platnosti unique_ptr zavolá delete na ukazovateli base_ptr.
        // To je ekvivalentné s delete base_ptr; a spôsobí rovnaký problém, ak vector nemá virtuálny deštruktor.
    
        return 0;
    } // Pamäť alokovaná pre resource nebude uvoľnená
    
  3. Konštruktory: Správanie konštruktorov std::vector (napríklad kopírovací, presúvací konštruktor) nemusí zodpovedať očakávaniam pre odvodenú triedu, ak pridáva vlastný stav alebo logiku.

Namiesto dedenia od std::vector sú lepšie prístupy:

  • Kompozícia: Použiť std::vector ako člen triedy. To umožňuje kontrolovať rozhranie a správanie novej triedy pomocou std::vector vo vnútri.
    #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 { // Poskytujeme prístup k vektoru podľa potreby
            return data;
        }
        // Deštruktor MyContainer správne zavolá deštruktor data
        ~MyContainer() {
             std::cout << "MyContainer deštruktor" << std::endl;
        }
    };
    
    int main() {
        MyContainer mc;
        mc.add(1);
        std::cout << "Veľkosť dát: " << mc.get_data().size() << std::endl;
        return 0;
    } // Deštruktor MyContainer je volaný, ktorý následne volá deštruktor std::vector
    
  • Voľné funkcie a algoritmy: Rozširovať funkcionalitu std::vector pomocou bežných funkcií alebo použiť štandardné algoritmy.

Tieto prístupy sú flexibilnejšie, bezpečnejšie a zodpovedajú princípom OOP a návrhu knižníc v C++. Štandardné kontajnery nie sú určené na použitie ako základné triedy.