14.4.1. Инициализация массива, распределенного из хипа A

14.4.1. Инициализация массива, распределенного из хипа A

По умолчанию инициализация массива объектов, распределенного из хипа, проходит в два этапа: выделение памяти для массива, к каждому элементу которого применяется конструктор по умолчанию, если он определен, и последующее присваивание значения каждому элементу.

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

#include utility

#include vector

#include new

#include cstddef

#include "Accounts.h"

typedef pairchar*, double

value_pair;

/* init_heap_array()

* объявлена как статическая функция-член

* обеспечивает выделение памяти из хипа и инициализацию

* массива объектов

* init_values: пары начальных значений элементов массива

* elem_count: число элементов в массиве

* если 0, то размером массива считается размер вектора

* init_values

*/

Account*

Account::

init_heap_array(

vectorvalue_pair &init_values,

vectorvalue_pair ::size_type elem_count = 0 )

{

vectorvalue_pair ::size_type

vec_size = init_value.size();

if ( vec_size == 0 && elem_count == 0 )

return 0;

// размер массива равен либо elem_count,

// либо, если elem_count == 0, размеру вектора ...

size_t elems = elem_count

? elem_count : vec_size();

// получить блок памяти для размещения массива

char *p = new char[sizeof(Account)*elems];

// по отдельности инициализировать каждый элемент массива

int offset = sizeof( Account );

for ( int ix = 0; ix elems; ++ix )

{

// смещение ix-ого элемента

// если пара начальных значений задана,

// передать ее конструктору;

// в противном случае вызвать конструктор по умолчанию

if ( ix vec_size )

new( p+offset*ix ) Account( init_values[ix].first,

init_values[ix].second );

else new( p+offset*ix ) Account;

}

// отлично: элементы распределены и инициализированы;

// вернуть указатель на первый элемент

return (Account*)p;

}

Необходимо заранее выделить блок памяти, достаточный для хранения запрошенного массива, как массив байт, чтобы избежать применения к каждому элементу конструктора по умолчанию. Это делается в такой инструкции:

char *p = new char[sizeof(Account)*elems];

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

for ( int ix = 0; ix

В разделе 14.3 говорилось, что оператор размещения new позволяет применить конструктор класса к уже выделенной области памяти. В данном случае мы используем new для поочередного применения конструктора класса Account к каждому из выделенных элементов массива. Поскольку при создании инициализированного массива мы подменили стандартный механизм выделения памяти, то должны сами позаботиться о ее освобождении. Оператор delete работать не будет:

delete [] ps;

Почему? Потому что ps (мы предполагаем, что эта переменная была инициализирована вызовом init_heap_array()) указывает на блок памяти, полученный не с помощью стандартного оператора new, поэтому число элементов в массиве компилятору неизвестно. Так что всю работу придется сделать самим:

void

Account::

dealloc_heap_array( Account *ps, size_t elems )

{

for ( int ix = 0; ix elems; ++ix )

ps[ix].Account::~Account();

delete [] reinterpret_castchar*(ps);

}

Если в функции инициализации мы пользовались арифметическими операциями над указателями для доступа к элементам:

new( p+offset*ix ) Account;

то здесь мы обращаемся к ним, задавая индекс в массиве ps:

ps[ix].Account::~Account();

Хотя и ps, и p адресуют одну и ту же область памяти, ps объявлен как указатель на объект класса Account, а p - как указатель на char. Индексирование p дало бы ix-й байт, а не ix-й объект класса Account. Поскольку с p ассоциирован не тот тип, что нужно, арифметические операции над указателями приходится программировать самостоятельно.

Мы объявляем обе функции статическими членами класса:

typedef pairchar*, double value_pair;

class Account {

public:

// ...

static Account* init_heap_array(

vector value_pair &init_values,

vector value_pair ::size_type elem_count = 0 );

static void dealloc_heap_array( Account*, size_t );

// ...

};