Sobes.tech
Middle+

Waarom zal de vector slecht werken als er van wordt geërfd?

sobes.tech AI

Antwoord van AI

Erfelijkheid van std::vector wordt niet aanbevolen vanwege het ontbreken van virtuele destructors en andere virtuele functies, wat de LSP (Liskov Substitutie Principe) schendt en problemen veroorzaakt bij polymorf gebruik.

Voorbeelden van problemen:

  1. Slicing-probleem: Bij het doorgeven van een object van een afgeleide klasse via waarde of referentie naar de basisklasse std::vector, gaan gegevens en gedrag die specifiek zijn voor de afgeleide klasse verloren.

    #include <vector>
    #include <iostream>
    
    class MyVector : public std::vector<int> {
    public:
        int my_data = 100;
        // Zonder virtuele destructor
        ~MyVector() {
            std::cout << "MyVector destructor" << std::endl;
        }
    };
    
    void process_vector(std::vector<int> vec) {
        // Ontvangt per waarde - slicing vindt plaats
        // Destructor van MyVector wordt niet aangeroepen
        std::cout << "Processing std::vector" << std::endl;
    }
    
    int main() {
        MyVector mv;
        mv.push_back(1);
        process_vector(mv); // Slicing van MyVector object
        return 0;
    } // Alleen std::vector destructor wordt aangeroepen
    
  2. Ontbreken van virtuele destructor: Als je een object van een afgeleide klasse verwijdert via een pointer naar de basisklasse std::vector, wordt de destructor van de afgeleide klasse niet aangeroepen, wat kan leiden tot resource leaks.

    #include <vector>
    #include <iostream>
    #include <memory> // Voor unique_ptr
    
    class DerivedVector : public std::vector<int> {
    public:
        int* resource;
        DerivedVector() : std::vector<int>(), resource(new int) {
            std::cout << "DerivedVector constructor" << std::endl;
        }
        // Ontbreekt virtuele destructor
        ~DerivedVector() {
            std::cout << "DerivedVector destructor" << std::endl;
            delete resource; // Mogelijk niet aangeroepen
        }
    };
    
    int main() {
        // Verwijdering via pointer naar basisklasse
        std::vector<int>* base_ptr = new DerivedVector();
        // Bij delete base_ptr wordt alleen std::vector destructor aangeroepen,
        // destructor van DerivedVector wordt niet aangeroepen, resource leaking
        delete base_ptr;
    
        // Voorbeeld met unique_ptr
        // std::unique_ptr<std::vector<int>> up = std::make_unique<DerivedVector>();
        // Bij het verlaten van scope roept unique_ptr delete aan op de raw pointer base_ptr.
        // Dit is gelijk aan delete base_ptr; en leidt tot hetzelfde probleem als vector geen virtuele destructor heeft.
    
        return 0;
    } // Geheugen voor resource wordt niet vrijgegeven
    
  3. Constructors: Het gedrag van constructors van std::vector (bijvoorbeeld kopieer- en move-constructors) kan niet voldoen aan de verwachtingen voor een afgeleide klasse als deze eigen toestand of logica toevoegt.

In plaats van te erven van std::vector, zijn de beste benaderingen:

  • Samenstelling: Gebruik std::vector als lid van een klasse. Dit stelt je in staat om de interface en het gedrag van de nieuwe klasse te controleren, door std::vector binnenin te gebruiken.
    #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 { // Toegang tot vector indien nodig
            return data;
        }
        // Destructor van MyContainer roept correct destructor van data aan
        ~MyContainer() {
             std::cout << "MyContainer destructor" << std::endl;
        }
    };
    
    int main() {
        MyContainer mc;
        mc.add(1);
        std::cout << "Data size: " << mc.get_data().size() << std::endl;
        return 0;
    } // Destructor van MyContainer wordt aangeroepen, die op zijn beurt destructor van std::vector aanroept
    
  • Vrije functies en algoritmen: Breid functionaliteit van std::vector uit met gewone functies of gebruik standaard algoritmen.

Deze benaderingen zijn flexibeler, veiliger en voldoen aan de principes van OOP en bibliotheekontwerp in C++. Standaard containers zijn niet bedoeld om als basisklassen te worden gebruikt.