Литмир - Электронная Библиотека
A
A

• Как запрограммировать класс

vector<X>
, если тип
X
не имеет значения по умолчанию?

• Как гарантировать, что элементы вектора будут уничтожены в конце работы с ним? 

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Должны ли мы вообще решать эти проблемы? Мы могли бы заявить: “Не создавайте векторы для типов, не имеющих значений по умолчанию” или “Не используйте векторы для типов, деструкторы которых могут вызвать проблемы”. Для конструкции, предназначенной для общего использования, такие ограничения довольно обременительны и создают впечатление, что разработчик не понял задачи или не думал о пользователях. Довольно часто такие подозрения оказываются правильными, но разработчики стандартной библиотеки к этой категории не относятся. Для того чтобы повторить стандартный класс vector, мы должны устранить две указанные выше проблемы.

Мы можем работать с типами, не имеющими значений по умолчанию, предоставив пользователю возможность задавать это значение самостоятельно.

template<class T> void vector<T>::resize(int newsize, T def = T());

Иначе говоря, используйте в качестве значения по молчанию объект, созданный конструктором

T()
, если пользователь не указал иначе. Рассмотрим пример.

vector<double> v1;

v1.resize(100);      // добавляем 100 копий объекта double(), т.е. 0.0

v1.resize(200, 0.0); // добавляем 200 копий числа 0.0 — упоминание

                     // излишне

v1.resize(300, 1.0); // добавляем 300 копий числа 1.0

struct No_default {

  No_default(int);   // единственный конструктор класса No_default

  // ...

};

vector<No_default> v2(10);     // ошибка: попытка создать 10

                               // No_default()

vector<No_default> v3;

v3.resize(100, No_default(2)); // добавляем 100 копий объектов

                               // No_default(2)

v3.resize(200);                // ошибка: попытка создать 200

                               // No_default()

Проблему, связанную с деструктором, устранить труднее. По существу, мы оказались в действительно трудной ситуации: в структуре данных часть данных проинициализирована, а часть — нет. До сих пор мы старались избегать неинициализированных данных и ошибок, которые ими порождаются. Теперь, как разработчики класса

vector
, мы столкнулись с проблемой, которой раньше, как пользователи класса
vector
, не имели.

Во-первых, мы должны найти способ для получения неинициализированной памяти и манипулирования ею. К счастью, стандартная библиотека содержит класс

allocator
, распределяющий неинициализированную память. Слегка упрощенный вариант приведен ниже.

template<class T> class allocator {

public:

  // ...

  T* allocate(int n);       // выделяет память для n объектов типа T

  void deallocate(T* p, int n); // освобождает память, занятую n

                            // объектами типа T, начиная с адреса p

  void construct(T* p, const T& v); // создает объект типа T

                                    // со значением v по адресу p

  void destroy(T* p);               // уничтожает объект T по адресу p

};

Если вам нужна полная информация по этому вопросу, обратитесь к книге The C++ Programming Language или к стандарту языка С++ (см. описание заголовка <memory> ), а также к разделу B.1.1. Тем не менее в нашей программе демонстрируются четыре фундаментальных операции, позволяющих выполнять следующие действия:

• Выделение памяти, достаточной для хранения объекта типа

T
без инициализации.

• Создание объекта типа

T
в неинициализированной памяти.

• Уничтожение объекта типа

T
и возвращение памяти в неинициализированное состояние.

• Освобождение неинициализированной памяти, достаточной для хранения объекта типа

T
без инициализации.

Не удивительно, что класс

allocator
— то, что нужно для реализации функции
vector&lt;T&gt;::reserve()
. Начнем с того, что включим в класс
vector
параметр класса
allocator
.

template&lt;class T, class A = allocator&lt;T&gt; &gt; class vector {

  A alloc;  // используем объект класса allocator для работы

            // с памятью, выделяемой для элементов

  // ...

};

Кроме распределителя памяти, используемого вместо оператора

new
, остальная часть описания класса
vector
не отличается от прежнего. Как пользователи класса
vector
, мы можем игнорировать распределители памяти, пока сами не захотим, чтобы класс
vector
управлял памятью, выделенной для его элементов, нестандартным образом. Как разработчики класса
vector
и как студенты, пытающиеся понять фундаментальные проблемы и освоить основные технологии программирования, мы должны понимать, как вектор работает с неинициализированной памятью, и предоставить пользователям правильно сконструированные объекты. Единственный код, который следует изменить, — это функции-члены класса
vector
, непосредственно работающие с памятью, например функция
vector&lt;T&gt;::reserve()
.

template&lt;class T, class A&gt;

void vector&lt;T,A&gt;::reserve(int newalloc)

261
{"b":"847443","o":1}