Dlaczego wektor będzie źle działał, jeśli od niego dziedziczymy?
Odpowiedź od AI
sobes.tech AI
Dziedziczenie po std::vector nie jest zalecane ze względu na brak wirtualnych destruktorów i innych funkcji wirtualnych, co narusza zasadę podstawiania Liskov i prowadzi do problemów przy używaniu polimorficznym.
Przykłady problemów:
-
Problem cięcia (slicing): Przekazywanie obiektu klasy pochodnej przez wartość lub referencję do klasy bazowej
std::vectorspowoduje utratę danych i zachowania specyficzne dla klasy pochodnej.#include <vector> #include <iostream> class MyVector : public std::vector<int> { public: int my_data = 100; // Brak wirtualnego destruktora ~MyVector() { std::cout << "Destruktor MyVector" << std::endl; } }; void process_vector(std::vector<int> vec) { // Przekazywanie przez wartość - slicing // Destruktor MyVector nie zostanie wywołany std::cout << "Przetwarzanie std::vector" << std::endl; } int main() { MyVector mv; mv.push_back(1); process_vector(mv); // Slicing obiektu MyVector return 0; } // Tutaj zostanie wywołany tylko destruktor std::vector -
Brak wirtualnego destruktora: Jeśli usuwasz obiekt klasy pochodnej przez wskaźnik do klasy bazowej
std::vector, destruktor klasy pochodnej nie zostanie wywołany, co może prowadzić do wycieków zasobów.#include <vector> #include <iostream> #include <memory> // dla std::unique_ptr class DerivedVector : public std::vector<int> { public: int* resource; DerivedVector() : std::vector<int>(), resource(new int) { std::cout << "Konstruktor DerivedVector" << std::endl; } // Brak wirtualnego destruktora ~DerivedVector() { std::cout << "Destruktor DerivedVector" << std::endl; delete resource; // Może nie zostać wywołany } }; int main() { // Usuwanie przez wskaźnik do klasy bazowej std::vector<int>* base_ptr = new DerivedVector(); // Podczas delete base_ptr, zostanie wywołany tylko destruktor std::vector, // destruktor DerivedVector nie zostanie wywołany, co powoduje wyciek resource delete base_ptr; // Przykład z std::unique_ptr // std::unique_ptr<std::vector<int>> up = std::make_unique<DerivedVector>(); // up->push_back(5); // Po wyjściu ze scope, unique_ptr wywoła delete na wskaźniku surowym base_ptr. // To jest równoważne z delete base_ptr; i spowoduje ten sam problem, jeśli vector nie ma wirtualnego destruktora. return 0; } // Pamięć przydzielona dla resource nie zostanie zwolniona -
Konstruktory: Zachowanie konstruktorów
std::vector(np. kopiowania, przenoszenia) może nie odpowiadać oczekiwaniom dla klasy pochodnej, jeśli dodaje ona własny stan lub logikę.
Zamiast dziedziczyć po std::vector, najlepszymi podejściami są:
- Kompozycja: Używać
std::vectorjako członka klasy. Pozwala to kontrolować interfejs i zachowanie nowej klasy, korzystając zstd::vectorwewnątrz.#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 { // Udostępniamy dostęp do wektora w razie potrzeby return data; } // Destruktor MyContainer poprawnie wywoła destruktor data ~MyContainer() { std::cout << "Destruktor MyContainer" << std::endl; } }; int main() { MyContainer mc; mc.add(1); std::cout << "Rozmiar danych: " << mc.get_data().size() << std::endl; return 0; } // Destruktor MyContainer jest wywoływany, który z kolei wywołuje destruktor std::vector - Funkcje wolne i algorytmy: Rozszerzać funkcjonalność
std::vectorza pomocą zwykłych funkcji lub korzystając z algorytmów standardowych.
Te podejścia są bardziej elastyczne, bezpieczniejsze i zgodne z zasadami OOP oraz projektowania bibliotek w C++. Standardowe kontenery nie są przeznaczone do używania jako klasy bazowe.