Sobes.tech
Middle+

¿Por qué el vector funcionará mal si se hereda de él?

sobes.tech AI

Respuesta de la IA

La herencia de std::vector no se recomienda debido a la falta de destructores virtuales y otras funciones virtuales, lo que viola el principio de sustitución de Liskov y conduce a problemas en el uso polimórfico.

Ejemplos de problemas:

  1. Problema de corte (slicing): Al pasar un objeto de una clase derivada por valor o referencia a una clase base std::vector, se perderán los datos y comportamientos específicos de la clase derivada.

    #include <vector>
    #include <iostream>
    
    class MyVector : public std::vector<int> {
    public:
        int my_data = 100;
        // Sin destructor virtual
        ~MyVector() {
            std::cout << "Destructor de MyVector" << std::endl;
        }
    };
    
    void process_vector(std::vector<int> vec) {
        // Se pasa por valor - ocurre corte
        // El destructor de MyVector no será llamado
        std::cout << "Procesando std::vector" << std::endl;
    }
    
    int main() {
        MyVector mv;
        mv.push_back(1);
        process_vector(mv); // Ocurre corte del objeto MyVector
        return 0;
    } // Aquí solo se llamará al destructor de std::vector
    
  2. Falta de destructor virtual: Si eliminas un objeto de una clase derivada a través de un puntero a la clase base std::vector, el destructor de la clase derivada no se llamará, lo que puede provocar fugas de recursos.

    #include <vector>
    #include <iostream>
    #include <memory> // Para unique_ptr
    
    class DerivedVector : public std::vector<int> {
    public:
        int* resource;
        DerivedVector() : std::vector<int>(), resource(new int) {
            std::cout << "Constructor de DerivedVector" << std::endl;
        }
        // Sin destructor virtual
        ~DerivedVector() {
            std::cout << "Destructor de DerivedVector" << std::endl;
            delete resource; // Puede no ser llamado
        }
    };
    
    int main() {
        // Eliminación a través de puntero a la clase base
        std::vector<int>* base_ptr = new DerivedVector();
        // Al hacer delete en base_ptr, solo se llamará al destructor de std::vector,
        // el destructor de DerivedVector no se llamará, causando fuga de resource
        delete base_ptr;
    
        // Ejemplo con unique_ptr
        // std::unique_ptr<std::vector<int>> up = std::make_unique<DerivedVector>();
        // up->push_back(5);
        // Al salir del scope, unique_ptr llamará a delete en el puntero crudo base_ptr.
        // Esto es equivalente a delete base_ptr; y tendrá el mismo problema si el vector no tiene destructor virtual.
    
        return 0;
    } // La memoria asignada para resource no será liberada
    
  3. Constructores: El comportamiento de los constructores de std::vector (por ejemplo, copia y movimiento) puede no ajustarse a las expectativas para la clase derivada si añade su propio estado o lógica.

En lugar de heredar de std::vector, las mejores prácticas son:

  • Composición: Usar std::vector como miembro de la clase. Esto permite controlar la interfaz y el comportamiento de la nueva clase, usando std::vector internamente.
    #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 { // Proporcionar acceso al vector si es necesario
            return data;
        }
        // El destructor de MyContainer llamará correctamente al destructor de data
        ~MyContainer() {
             std::cout << "Destructor de MyContainer" << std::endl;
        }
    };
    
    int main() {
        MyContainer mc;
        mc.add(1);
        std::cout << "Tamaño de data: " << mc.get_data().size() << std::endl;
        return 0;
    } // Se llama al destructor de MyContainer, que a su vez llama al destructor de std::vector
    
  • Funciones libres y algoritmos: Extender la funcionalidad de std::vector mediante funciones normales o usar algoritmos estándar.

Estas prácticas son más flexibles, seguras y cumplen con los principios de POO y diseño de bibliotecas en C++. Los contenedores estándar no están destinados a usarse como clases base.