Опубликован: 10.10.2006 | Уровень: специалист | Доступ: платный
Лекция 5:

Классы

5.5.4 Объекты класса как члены

Рассмотрим пример:

class classdef {
  table members;
  int no_of_members;
  // ...
  classdef(int size);
  ~classdef();
};

Цель этого определения, очевидно, в том, чтобы classdef содержал член, являющийся таблицей размером size, но есть сложность: надо обеспечить вызов конструктора table::table() с параметром size. Это можно сделать, например, так:

classdef::classdef(int size)
 :members(size)
         {
 no_of_members = size;
 // ...
}

Параметр для конструктора члена (т.е. для table::table() ) указывается в определении (но не в описании) конструктора класса, содержащего член (т.е. в определении classdef::classdef() ). Конструктор для члена будет вызываться до выполнения тела того конструктора, который задает для него список параметров.

Аналогично можно задать параметры для конструкторов других членов (если есть еще другие члены):

class classdef {
  table members;
  table friends;
  int no_of_members;
  // ...
  classdef(int size);
  ~classdef();
};

Списки параметров для членов отделяются друг от друга запятыми (а не двоеточиями), а список инициализаторов для членов можно задавать в произвольном порядке:

classdef::classdef(int size)
: friends(size), members(size), no_of_members(size)
{
  // ...
}

Конструкторы вызываются в том порядке, в котором они заданы в описании класса.

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

Если конструктору члена не требуется параметров, то и не нужно задавать никаких списков параметров. Так, поскольку конструктор table::table() был определен со стандартным значением параметра, равным 15, достаточно такого определения:

classdef::classdef(int size)
   : members(size), no_of_members(size)
          {
  // ...
}

Тогда размер таблицы friends будет равен 15.

Если уничтожается объект класса, который сам содержит объекты класса (например, classdef ), то вначале выполняется тело деструктора объемлющего класса, а затем деструкторы членов в порядке, обратном их описанию.

Рассмотрим вместо вхождения объектов класса в качестве членов традиционное альтернативное ему решение: иметь в классе указатели на члены и инициализировать члены в конструкторе:

class classdef {
   table* members;
   table* friends;
   int no_of_members;
   // ...
};

          classdef::classdef(int size)
          {
  members = new table(size);
  friends = new table;  // используется стандартный
     // размер table
  no_of_members = size;
  // ...
}

Поскольку таблицы создавались с помощью операции new, они должны уничтожаться операцией delete:

classdef::~classdef()
{
 // ...
 delete members;
 delete friends;
}

Такие отдельно создаваемые объекты могут оказаться полезными, но учтите, что members и friends указывают на независимые от них объекты, каждый из которых надо явно размещать и удалять. Кроме того, указатель и объект в свободной памяти суммарно занимают больше места, чем объект-член.

5.5.5 Массивы объектов класса

Чтобы можно было описать массив объектов класса с конструктором, этот класс должен иметь стандартный конструктор, т.е. конструктор, вызываемый без параметров. Например, в соответствии с определением

table tbl[10];

будет создан массив из 10 таблиц, каждая из которых инициализируется вызовом table::table(15), поскольку вызов table::table() будет происходить с фактическим параметром 15.

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

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

void f()
{
 table* t1 = new table;
 table* t2 = new table[10];
 delete t1;  // удаляется одна таблица
 delete t2;  // неприятность:
         // на самом деле удаляется 10 таблиц
}

В данном случае программист должен указать, что t2 - указатель на массив:

void g(int sz)
{
   table* t1 = new table;
   table* t2 = new table[sz];
   delete t1;
   delete[] t2;
}

Функция размещения хранит число элементов для каждого размещаемого массива. Требование использовать для удаления массивов только операцию delete[] освобождает функцию размещения от обязанности хранить счетчики числа элементов для каждого массива. Исполнение такой обязанности в реализациях С++ вызывало бы существенные потери времени и памяти и нарушило совместимость с С.

5.5.6 Небольшие объекты

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

Вернемся к классу name, который использовался в примерах с table. Он мог бы определяться так:

struct name {
  char* string;
  name* next;
  double value;

  name(char*, double, name*);
  ~name();

  void* operator new(size_t);
  void operator delete(void*, size_t);
private:
  enum { NALL = 128 };
  static name* nfree;
};

Функции name::operator new() и name::operator delete() будут использоваться (неявно) вместо глобальных функций operator new() и operator delete(). Программист может для конкретного типа написать более эффективные по времени и памяти функции размещения и удаления, чем универсальные функции operator new() и operator delete(). Можно, например, разместить заранее "куски" памяти, достаточной для объектов типа name, и связать их в список; тогда операции размещения и удаления сводятся к простым операциям со списком. Переменная nfree используется как начало списка неиспользованных кусков памяти:

void* name::operator new(size_t)
 {
   register name* p = nfree;  // сначала выделить

   if (p)
  nfree = p->next;
   else { // выделить и связать в список
  name* q = (name*) new char[NALL*sizeof(name) ];
  for (p=nfree=&q[NALL-1]; q<p; p--) p->next = p-1;
  (p+1)->next = 0;
   }

   return p;
}

Распределитель памяти, вызываемый new, хранит вместе с объектом его размер, чтобы операция delete выполнялась правильно. Этого дополнительного расхода памяти можно легко избежать, если использовать распределитель, рассчитанный на конкретный тип. Так, на машине автора функция name::operator new() для хранения объекта name использует 16 байтов, тогда как стандартная глобальная функция operator new() использует 20 байтов.

Отметим, что в самой функции name::operator new() память нельзя выделять таким простым способом:

name* q= new name[NALL];

Это вызовет бесконечную рекурсию, т.к. new будет вызывать name::name().

Освобождение памяти обычно тривиально:

void name::operator delete(void* p, size_t)
{
  ((name*)p)->next = nfree;
  nfree = (name*) p;
}

Приведение параметра типа void* к типу name* необходимо, поскольку функция освобождения вызывается после уничтожения объекта, так что больше нет реального объекта типа name, а есть только кусок памяти размером sizeof(name). Параметры типа size_t в приведенных функциях name::operator new() и name::operator delete() не использовались. Отметим, что наши функции размещения и удаления используются только для объектов типа name, но не для массивов names.

Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?

алексей оглы
алексей оглы
Россия
рафич Салахиев
рафич Салахиев
Россия