¿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:
-
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 -
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 -
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::vectorcomo miembro de la clase. Esto permite controlar la interfaz y el comportamiento de la nueva clase, usandostd::vectorinternamente.#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::vectormediante 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.