Допустим, что у нас нет бесконечной памяти и сборщика мусора. На какие средства управления памятью может рассчитывать создатель контейнера, например, класса Vector? Для случая таких простых элементов, как int, очевидно, надо просто копировать их в контейнер. Столь же очевидно, что для других типов, таких, как абстрактный класс Shape, в контейнере следует хранить указатель. Создатель библиотеки должен предусмотреть оба варианта. Приведем набросок очевидного решения:
template<class T> Vector { T* p; int sz; public: Vector(int s) { p = new T[sz=s]; } // ... };
Если пользователь не будет заносить в контейнер вместо указателей на объекты сами объекты типа Shape, то это решение подходит для обоих вариантов.
Vector<Shape*> vsp(200); // нормально Vector<Shape> vs(200); // ошибка при трансляции
К счастью, транслятор отслеживает попытку создать массив объектов абстрактного базового класса Shape.
Однако, если используются указатели, создатель библиотеки и пользователь должны договориться, кто будет удалять хранимые в контейнере объекты. Рассмотрим пример:
void f() // противоречивое использование средств // управления памятью { Vector<Shape*> v(10); Circle* cp = new Circle; v[0] = cp; v[1] = new Triangle; Square s; v[2] = &s; delete cp; // не удаляет объекты, на которые настроены // указатели, находящиеся в контейнере }
Если использовать реализацию класса Vector из §1.4.3, объект Triangle в этом примере навсегда останется в подвешенном состоянии (на него нет указателей), если только нет сборщика мусора. Главное в управлении памятью это - это корректность. Рассмотрим такой пример:
void g() // корректное использование средств управления памятью { Vector<Shape*> v(10); Circle* cp = new Circle; v[0] = cp; v[1] = new Triangle; Square s; v[2] = &s; delete cp; delete v[1]; }
Рассмотрим теперь такой векторный класс,который следит за удалением занесенных в него указателей:
template<class T> MVector { T* p; int sz; public: MVector(int s); ~MVector(); // ... };