Sobes.tech
Middle+

Pourquoi le vecteur fonctionnera-t-il mal s'il en hérite?

sobes.tech IA

Réponse de l'IA

L’héritage de std::vector n’est pas recommandé en raison de l’absence de destructeurs virtuels et d’autres fonctions virtuelles, ce qui viole le principe de substitution de Liskov et entraîne des problèmes lors de l’utilisation polymorphe.

Exemples de problèmes:

  1. Problème de slicing: Lors du passage d’un objet de classe dérivée par valeur ou référence à une classe de base std::vector, les données et comportements spécifiques à la classe dérivée seront perdus.

    #include <vector>
    #include <iostream>
    
    class MyVector : public std::vector<int> {
    public:
        int my_data = 100;
        // Sans destructeur virtuel
        ~MyVector() {
            std::cout << "Destructeur de MyVector" << std::endl;
        }
    };
    
    void process_vector(std::vector<int> vec) {
        // Passage par valeur - slicing
        // Le destructeur de MyVector ne sera pas appelé
        std::cout << "Traitement de std::vector" << std::endl;
    }
    
    int main() {
        MyVector mv;
        mv.push_back(1);
        process_vector(mv); // Slicing de l’objet MyVector
        return 0;
    } // Seul le destructeur de std::vector sera appelé
    
  2. Absence de destructeur virtuel: Si vous supprimez un objet de classe dérivée via un pointeur vers la classe de base std::vector, le destructeur de la classe dérivée ne sera pas appelé, ce qui peut entraîner des fuites de ressources.

    #include <vector>
    #include <iostream>
    #include <memory> // Pour unique_ptr
    
    class DerivedVector : public std::vector<int> {
    public:
        int* resource;
        DerivedVector() : std::vector<int>(), resource(new int) {
            std::cout << "Constructeur de DerivedVector" << std::endl;
        }
        // Sans destructeur virtuel
        ~DerivedVector() {
            std::cout << "Destructeur de DerivedVector" << std::endl;
            delete resource; // Peut ne pas être appelé
        }
    };
    
    int main() {
        // Suppression via un pointeur vers la classe de base
        std::vector<int>* base_ptr = new DerivedVector();
        // Lors du delete de base_ptr, seul le destructeur de std::vector sera appelé,
        // le destructeur de DerivedVector ne sera pas appelé, causant une fuite de resource
        delete base_ptr;
    
        // Exemple avec unique_ptr
        // std::unique_ptr<std::vector<int>> up = std::make_unique<DerivedVector>();
        // up->push_back(5);
        // À la sortie du scope, unique_ptr appellera delete sur le pointeur brut base_ptr.
        // C’est équivalent à delete base_ptr; et causera le même problème si le vecteur n’a pas de destructeur virtuel.
    
        return 0;
    } // La mémoire allouée pour resource ne sera pas libérée
    
  3. Constructeurs: Le comportement des constructeurs de std::vector (par exemple, copie, déplacement) peut ne pas correspondre aux attentes pour une classe dérivée si elle ajoute son propre état ou logique.

Au lieu d’hériter de std::vector, il est préférable d’utiliser:

  • Composition: Utiliser std::vector comme membre de la classe. Cela permet de contrôler l’interface et le comportement de la nouvelle classe, en utilisant std::vector en interne.
    #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 { // Fournir l’accès au vecteur si nécessaire
            return data;
        }
        // Le destructeur de MyContainer appellera correctement le destructeur de data
        ~MyContainer() {
             std::cout << "Destructeur de MyContainer" << std::endl;
        }
    };
    
    int main() {
        MyContainer mc;
        mc.add(1);
        std::cout << "Taille des données: " << mc.get_data().size() << std::endl;
        return 0;
    } // Le destructeur de MyContainer est appelé, qui appelle à son tour le destructeur de std::vector
    
  • Fonctions libres et algorithmes: Étendre la fonctionnalité de std::vector avec des fonctions ordinaires ou utiliser des algorithmes standards.

Ces approches sont plus flexibles, sûres et respectent les principes de la POO et de la conception de bibliothèques en C++. Les conteneurs standards ne sont pas destinés à être utilisés comme classes de base.