Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

676

class NoName { public:

~NoName(); // ...

private:

char *pstring; int ival; double dval;

};

Упражнение 14.7

Необходим ли деструктор для класса, который вы выбрали в упражнении 14.3? Если нет, объясните почему. В противном случае предложите реализацию.

Упражнение 14.8

void mumble( const char *name, fouble balance, char acct_type )

{

Account acct;

if ( ! name ) return;

if ( balance <= 99 ) return;

switch( acct_type ) { case 'z': return; case 'a':

case 'b': return;

}

// ...

Сколько раз вызываются деструкторы в следующем фрагменте:

}

14.4. Массивы и векторы объектов

Массив объектов класса определяется точно так же, как массив элементов встроенного типа. Например:

Account table[ 16 ];

определяет массив из 16 объектов Account. Каждый элемент по очереди инициализируется конструктором по умолчанию. Можно и явно передать конструкторам аргументы внутри заключенного в фигурные скобки списка инициализации массива. Строка:

Account pooh_pals[] = { "Piglet", "Eeyore", "Tigger" };

определяет массив из трех элементов, инициализируемых конструкторами:

С++ для начинающих

 

677

 

Account( "Piglet", 0.0

);

// первый элемент (Пятачок)

 

 

Account( "Eeyore", 0.0

);

// второй элемент (Иа-Иа)

 

Account( "Tigger", 0.0

);

// третий элемент (Тигра)

 

 

 

 

 

Один аргумент можно задать явно, как в примере выше. Если же необходимо передать

Account pooh_pals[] = {

Account( "Piglet", 1000.0 ),

Account( "Eeyore", 1000.0 ),

Account( "Tigger", 1000.0 )

несколько аргументов, то придется воспользоваться явным вызовом конструктора:

};

Чтобы включить в список инициализации массива конструктор по умолчанию, мы

Account pooh_pals[] =

{

 

// Бука

Account( "Woozle",

10.0 ),

),

Account( "Heffalump", 10.0

// Слонопотам

Account();

 

 

 

употребляем явный вызов с пустым списком параметров:

};

Account pooh_pals[3] = {

Account( "Woozle", 10.0 ),

Account( "Heffalump", 10.0 )

Эквивалентный массив из трех элементов можно объявить и так:

};

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

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

pooh_pals[0];

обращается к Piglet, а

pooh_pals[1];

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

pooh_pals[1]._name != pooh_pals[2]._name;

С++ для начинающих

678

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

Объявление

Account *pact = new Account[ 10 ];

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

Чтобы уничтожить массив, адресованный указателем pact, необходимо применить

// увы! это не совсем правильно

оператор delete. Однако написать delete pact;

недостаточно, так как pact при этом не идентифицируется как массив объектов. В результате деструктор класса Account применяется лишь к первому элементу массива. Чтобы применить его к каждому элементу, мы должны включить пустую пару скобок

//правильно:

//показывает, что pact адресует массив

между оператором delete и адресом удаляемого объекта: delete [] pact;

Пустая пара скобок говорит о том, что pact адресует именно массив. Компилятор определяет, сколько в нем элементов, и применяет деструктор к каждому из них.

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

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

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

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

С++ для начинающих

679

#include <utility> #include <vector > #include <new> #include <cstddef> #include "Accounts.h"

typedef pair<char*, double> value_pair;

/* init_heap_array()

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

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

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

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

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

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

*init_values

*/

Account*

Account:: init_heap_array(

vector<value_pair> &init_values, vector<value_pair>::size_type elem_count = 0 )

{

vector<value_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;

}

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

С++ для начинающих

680

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

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

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

{

if ( ix < vec_size )

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

else new( p+offset*ix ) Account;

задана пара начальных значений, либо конструктор по умолчанию:

}

В разделе 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_cast<char*>(ps);

неизвестно. Так что всю работу придется сделать самим:

}

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

new( p+offset*ix ) Account;

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

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

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