Por que o vetor funcionará mal se herdar dele?
sobes.tech IA
Resposta da IA
A herança de std::vector não é recomendada devido à ausência de destrutores virtuais e outras funções virtuais, o que viola o princípio de substituição de Liskov e leva a problemas na utilização polimórfica.
Exemplos de problemas:
-
Problema de slicing: Ao passar um objeto de classe derivada por valor ou referência para uma classe base
std::vector, os dados e comportamentos específicos da classe derivada serão perdidos.#include <vector> #include <iostream> class MyVector : public std::vector<int> { public: int my_data = 100; // Sem destrutor virtual ~MyVector() { std::cout << "Destrutor de MyVector" << std::endl; } }; void process_vector(std::vector<int> vec) { // Passagem por valor - slicing // O destrutor de MyVector não será chamado std::cout << "Processando std::vector" << std::endl; } int main() { MyVector mv; mv.push_back(1); process_vector(mv); // slicing do objeto MyVector return 0; } // Aqui apenas o destrutor de std::vector será chamado -
Falta de destrutor virtual: Se você excluir um objeto de classe derivada através de um ponteiro para a classe base
std::vector, o destrutor da classe derivada não será chamado, o que pode levar a vazamentos 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 << "Construtor de DerivedVector" << std::endl; } // Sem destrutor virtual ~DerivedVector() { std::cout << "Destrutor de DerivedVector" << std::endl; delete resource; // Pode não ser chamado } }; int main() { // Exclusão através de um ponteiro para a classe base std::vector<int>* base_ptr = new DerivedVector(); // Ao fazer delete em base_ptr, apenas o destrutor de std::vector será chamado, // o destrutor de DerivedVector não será chamado, causando vazamento de resource delete base_ptr; // Exemplo com unique_ptr // std::unique_ptr<std::vector<int>> up = std::make_unique<DerivedVector>(); // up->push_back(5); // Ao sair do escopo, unique_ptr chamará delete no ponteiro bruto base_ptr. // Isso é equivalente a delete base_ptr; e causará o mesmo problema se o vector não tiver destrutor virtual. return 0; } // A memória alocada para resource não será liberada -
Construtores: O comportamento dos construtores de
std::vector(por exemplo, cópia, movimento) pode não corresponder às expectativas para uma classe derivada se ela adicionar seu próprio estado ou lógica.
Em vez de herdar de std::vector, as melhores abordagens são:
- Composição: Usar
std::vectorcomo membro da classe. Isso permite controlar a interface e o comportamento da nova classe, 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 { // Fornecer acesso ao vetor se necessário return data; } // O destrutor de MyContainer chamará corretamente o destrutor de data ~MyContainer() { std::cout << "Destrutor de MyContainer" << std::endl; } }; int main() { MyContainer mc; mc.add(1); std::cout << "Tamanho dos dados: " << mc.get_data().size() << std::endl; return 0; } // O destrutor de MyContainer é chamado, que por sua vez chama o destrutor de std::vector - Funções livres e algoritmos: Estender a funcionalidade de
std::vectorcom funções comuns ou usar algoritmos padrão.
Essas abordagens são mais flexíveis, seguras e seguem os princípios de POO e de design de bibliotecas em C++. Os contêineres padrão não são destinados a serem usados como classes base.