SlideShare a Scribd company logo
Углубленное
программирование
на языке C++
Алексей Петров
Лекция №4. Специальные
вопросы наследования
и полиморфизма
1. Инициализация, копирование,
преобразование и уничтожение объектов
(продолжение). Идиома RAII.
2. Раннее и позднее связывание. Перегрузка
и перекрытие членов класса.
3. Виртуальные функции. Абстрактные
классы. Множественное и виртуальное
наследование.
4. RTTI и операции приведения типов.
Производительность и безопасность
полиморфизма и средств поддержки RTTI.
5. Постановка задач к практикуму №4.

2
Инициализация без конструктора (1 / 2)
Класс, все члены которого открыты, может задействовать
механизм явной позиционной инициализации, ассоциирующий
значения в списке инициализации с членами данных в
соответствии с их порядком.
class Test
{
public:
int
int_prm;
double dbl_prm;
string str_prm;
};
// …
Test test = { 1, -3.14, "dictum factum" };
3
Инициализация без конструктора (2 / 2)
Преимуществами такой техники выступают:
 скорость и эффективность, особо значимые при выполнении во
время запуска программы (для глобальных объектов).

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

4
Конструкторы по умолчанию (1 / 2)
Конструктор по умолчанию не требует задания значений его
параметров, хотя таковые могут присутствовать в сигнатуре.
class Test
{
public:
Test(int ipr = 0, double dpr = 0.0);
/* … */
};

Наличие формальных параметров в конструкторе по умолчанию
позволяет сократить общее число конструкторов и объем
исходного кода.
5
Конструкторы по умолчанию (2 / 2)
Если в классе определен хотя бы один конструктор с параметрами,
то при использовании класса со стандартными контейнерами и
динамическими массивами экземпляров конструктор по
умолчанию обязателен.
Test *tests = new Test[TEST_PLAN_SIZE];

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

class Test
{
public:
Test(int prm) : _prm (prm) {}
private:
int
_prm;
};
// все вызовы конструктора допустимы и эквивалентны
Test
test1(10),
test2 = Test(10),
test3 = 10;
// для одного аргумента

7
Массивы объектов: пример

// массивы объектов класса определяются
// аналогично массивам объектов базовых типов
// для конструктора с одним аргументом
Test testplan1[] = { 10, -5, 0, 127 };
// для конструктора с несколькими аргументами
Test testplan2[5] = {
Test(10, 0.1),
Test(-5, -3.6),
Test(0, 0.0),
Test() // если есть конструктор по умолчанию
};

8
Закрытые и защищенные конструкторы
Описание конструктора класса как защищенного или закрытого
дает возможность ограничить или полностью запретить отдельные
способы создания объектов класса.
В большинстве случаев закрытые и защищенные конструкторы
используются для:
 предотвращения копирования одного объекта в другой;

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

9
Почленная инициализация
и присваивание (1 / 2)
Почленная инициализация по умолчанию — механизм
инициализации одного объекта класса другим объектом того же
класса, который активизируется независимо от наличия в
определении класса явного конструктора.
Почленная инициализация по умолчанию происходит в следующих
ситуациях:
 явная инициализация одного объекта другим;
 передача объекта класса в качестве аргумента функции;
 передача объекта класса в качестве возвращаемого функцией значения;

 определение непустого стандартного последовательного контейнера;
 вставка объекта класса в стандартный контейнер.
10
Почленная инициализация
и присваивание (2 / 2)
Почленная инициализация по умолчанию подавляется при
наличии в определении класса конструктора копирования.
Запрет почленной инициализации по умолчанию осуществляется
одним из следующих способов:
 описание закрытого конструктора копирования (не действует для
методов класса и дружественных объектов);
 описание конструктора копирования без его определения (действует
всюду).

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

11
Конструкторы копирования
Конструктор копирования принимает в качестве единственного
параметра константную ссылку на существующий объект класса.
В случае отсутствия явного конструктора копирования в
определении класса производится почленная инициализация
объекта по умолчанию.
class Test
{
/* … */
Test(const Test &other);
/* … */
};
12
Конструкторы
и операции преобразования
Конструкторы преобразования служат для построения объектов
класса по одному или нескольким значениям иных типов.
Операции преобразования позволяют преобразовывать
содержимое объектов класса к требуемым типам данных.
class Test
{
// конструкторы преобразования
Test(const char *);
Test(const string &);
// операции преобразования
operator int
() { return int_prm; }
operator double () { return dbl_prm; }
/* … */
};
13
Деструкторы.
Виртуальные деструкторы (1 / 2)
Деструктор — не принимающий параметров и не возвращающий
результат метод класса, автоматически вызываемый при выходе
объекта из области видимости и применении к указателю на
объект класса операции delete.
class Test
{
/* … */
virtual ~Test();
};

Примечание: деструктор не вызывается при выходе из области
видимости ссылки или указателя на объект.
14
Деструкторы.
Виртуальные деструкторы (2 / 2)
Типичные задачи деструктора:
 сброс содержимого программных буферов в долговременные хранилища;
 освобождение (возврат) системных ресурсов, главным образом —
оперативной памяти;
 закрытие файлов или устройств;
 снятие блокировок, останов таймеров и т.д.

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

15
Идиома RAII
Закрепление за конструкторами функции захвата, выделения,
блокировки или инициализации ресурсов, а за деструкторами —
функции их возврата, освобождения и снятия установленных
блокировок:
 позволяет безопасно обрабатывать ошибки и исключения;
 составляет суть одной из важнейших идиом ОО-программирования RAII
(англ. Resource Acquisition Is Initialization — «получение ресурса есть
инициализация»).

Работа идиомы RAII в языке C++ основана, главным образом, на
гарантированном вызове деструкторов автоматических
переменных, являющихся экземплярами классов, при выходе из
соответствующих областей видимости.
16
Явный вызов деструкторов
Потребность в явном вызове деструктора обычно связана с
необходимостью уничтожить динамически размещенный объект
без освобождения памяти.
char *buf = new char[sizeof(Test)];
// "размещающий" вариант new
Test *ptc = new (buf) Test(100);

/* … */
ptc->~Test();
// вызов 1
Test *ptc = new (buf) Test(200);
/* … */
ptc->~Test(); // вызов 2
delete [] buf;
17
Список инициализации в конструкторе
Выполнение любого конструктора состоит из двух фаз:
 фаза явной (неявной) инициализации (обработка списка
инициализации) — предполагает начальную инициализацию членов
данных;
 фаза вычислений (исполнение тела конструктора) — предполагает
присваивание значений (в предварительно инициализированных
областях памяти).

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

18
Семантика переноса (C++11)
Введение в С++11 семантики переноса (англ. move semantics)
обогащает язык возможностями более тонкого и
эффективного управления памятью данных, устраняющего
копирование объектов там, где оно нецелесообразно.
Технически семантика переноса реализуется при помощи
ссылок на праводопустимые выражения и конструкторов
переноса.
Конструкторы переноса не создают точную копию своего
параметра, а перенастраивают параметр так, чтобы права
владения соответствующей областью памяти были переданы
вновь создаваемому объекту («заимствованы» последним).
Аналогично работают операции присваивания с переносом.
19
Конструктор переноса: пример (1 / 2)

class Alpha {
public:
Alpha();
Alpha(const Alpha &a); // конструктор копирования
Alpha(Alpha &&a);
// конструктор переноса
~Alpha();
private:
size_t sz;
double *d;
};
Alpha::Alpha() : sz(0), d(0) { }
Alpha::~Alpha() {
delete [] d;
}

20
Конструктор переноса: пример (2 / 2)

// конструктор копирования
Alpha::Alpha(const Alpha &a) : sz(a.sz)
{
d = new double[sz];
/* … */
for(size_t i = 0; i < sz; i++)
d[i] = a.d[i];
}

// конструктор переноса
Alpha::Alpha(Alpha &&a) : sz(a.sz)
{
d
= a.d;
// перенастройка параметра
a.d = nullptr; // C++11
a.sz = 0;
}
21
Наследование: ключевые понятия
Наследование содействует повторному использованию атрибутов
и методов класса, а значит, делает процесс разработки ПО более
эффективным. Возникающие между классами A и B отношения
наследования позволяют, например, говорить, что:
 класс A является базовым (родительским) классом, классом-предком,
надклассом (англ. superclass);
 класс B является производным (дочерним) классом, классом-потомком,
подклассом (англ. subclass).

Отношения наследования связывают классы в иерархию
наследования, вид которой зависит от числа базовых классов у
каждого производного:
 при одиночном наследовании иерархия имеет вид дерева;
 при множественном наследовании — вид направленного
ациклического графа (НАГ) произвольного вида.

22
Наследование: примеры (UML)

Одиночное наследование (слева), множественное наследование (в центре,
классы Alpha и Alpha (II) идентичны), виртуальное множественное
наследование (справа)
23
Полиморфизм подклассов
В том случае если базовый и производный классы имеют общий
открытый интерфейс, говорят, что производный класс
представляет собой подкласс базового.
Отношение между классом и подклассом, позволяющее указателю
или ссылке на базовый класс без вмешательства программиста
адресовать объект производного класса, возникает в C++
благодаря поддержке полиморфизма.
Полиморфизм позволяет предложить такую реализацию ядра
объектно-ориентированного приложения, которая не будет
зависеть от конкретных используемых подклассов.

24
Раннее и позднее связывание
В рамках классического объектного подхода, — а равно и
процедурного программирования, — адрес вызываемой функции
(метода класса) определяется на этапе компиляции (сборки). Такой
порядок связывания вызова функции и ее адреса получил
название раннего (статического).
Позднее (динамическое) связывание состоит в нахождении
(разрешении) нужной функции во время исполнения кода. При
этом работа по разрешению типов перекладывается с
программиста на компилятор.
В языке C++ динамическое связывание поддерживается
механизмом виртуальных методов класса, для работы с которыми
компиляторы строят таблицы виртуальных методов (англ. VMT,
virtual method table).
25
Базовые и производные классы
ОО-проектирование допускает существование классов, которые
могут выполнять чисто технические функции, моделировать
абстрактные сущности и отличаться функциональной неполнотой:
• не подлежащий реализации в виде экземпляров (объектов) базовый
класс может оставаться абстрактным. В противовес абстрактным
базовым классам классы, предполагающие создание экземпляров,
именуют конкретными;

• (абстрактные) базовые классы специфицируют открытые интерфейсы
иерархий и содержат общие для всех подклассов атрибуты и методы
(или их прототипы).

Множество подклассов любого базового класса ограничено
иерархией наследования, но потенциально бесконечно (ввиду
отсутствия пределов по расширению иерархии вглубь и вширь).
26
Определение наследования
Определение отношения наследования имеет вид (для
одиночного наследования):
// заголовок класса
class <имя производного класса> :
<уровень доступа> <имя базового класса>
// тело класса
{
/* … */
};

где <уровень доступа> — ключевое слово public, private или
protected, а <имя базового класса> — имя ранее определенного
(не описанного!) класса.
В зависимости от уровня доступа к членам базового класса
говорят об открытом, закрытом или защищенном наследовании.
27
Определение наследования: пример

// описание производного класса
// (не включает список базовых классов!)
class Deposit;
/* … */
// определения классов
class Account
{
/* … */
};
class Deposit : public Account
{
/* … */
};
28
Защищенные и закрытые члены класса
Атрибуты и методы базового класса, как правило, должны быть
непосредственно доступны для производных классов и
непосредственно недоступны для прочих компонентов программы.
В этом случае они помещаются в секцию protected, в результате
чего защищенные члены данных и методы базового класса:
 доступны производному классу (прямому потомку);
 недоступны классам вне рассматриваемой иерархии, глобальным
функциям и вызывающей программе.

Если наличие прямого доступа к члену класса со стороны
производных классов нежелательно, он вводится как закрытый.
Закрытые члены класса не наследуются потомками. Для доступа к
ним класс-потомок должен быть объявлен в классе-предке как
дружественный. Отношения дружественности не наследуются.
29
Перегрузка и перекрытие
членов класса
Члены данных базового класса могут перекрываться
одноименными членами данных производного класса, при этом
их типы не должны обязательно совпадать. (Для доступа к
члену базового класса его имя должно быть
квалифицировано.)
Методы базового и производного классов не образуют
множество перегруженных функций. В этом случае методы
производного класса не перегружают (англ. overload), а
перекрывают (англ. override) методы базового.
Для явного создания объединенного множества
перегруженных функций базового и производного классов
используется объявление using, которое вводит именованный
член базового класса в область видимости производного.

30
Перегрузка и перекрытие
членов класса: пример
Примечание: в область видимости производного класса попадают
все одноименные методы базового класса, а не только некоторые
из них.
class Account
{ /* … */
void display(const char *fmt);
void display(const int mode = 0);
};
class Deposit : public Account
{
void display(const string &fmt);
using Account::display;
/* … */
};

31
Порядок вызова конструкторов
производных классов (1 / 2)
Порядок вызова конструкторов объектов-членов, а также
базовых классов при построении объекта производного класса
не зависит от порядка их перечисления в списке инициализации
конструктора производного класса и является следующим:
 конструктор базового класса (если таковых несколько, конструкторы
вызываются в порядке перечисления имен классов в списке базовых
классов);
 конструктор объекта-члена (если таковых несколько, конструкторы
вызываются в порядке объявления членов данных в определении
класса);
 конструктор производного класса.

32
Порядок вызова конструкторов
производных классов (2 / 2)
Конструктор производного класса может вызывать конструкторы
классов, непосредственно являющихся базовыми для данного
(прямых предков), и — без учета виртуального наследования —
только их.
Примечание: правильно спроектированный конструктор
производного класса не должен инициализировать атрибуты
базового класса напрямую (путем присваивания значений).

33
Список инициализации при
наследовании: пример
class Alpha {
public:
// Alpha();
Alpha(int i); /* … */
};
class Beta : public Alpha {
public:
Beta() : _s("dictum factum") { }
// Beta() : Alpha(), _s("dictum factum") { }
Beta(int i, string s) : Alpha(i), _s(s) { }
protected:
string _s; /* … */
};

34
Порядок вызова деструкторов
производных классов
Порядок вызова деструкторов при уничтожении объекта
производного класса прямо противоположен порядку вызова
конструкторов и является следующим:
 деструктор производного класса;
 деструктор объекта-члена (или нескольких);
 деструктор базового класса (или нескольких).

Взаимная противоположность порядка вызова конструкторов и
деструкторов является строгой гарантией языка C++.

35
Виртуальные функции (1 / 2)
Методы, результат разрешения вызова которых зависит от
«реального» (динамического) типа объекта, доступного по
указателю или ссылке, называются виртуальными и при
определении в базовом классе снабжаются спецификатором
virtual.
Примечание: в этом контексте тип непосредственно определяемого
экземпляра, ссылки или указателя на объект называется
статическим. Для самого объекта любого типа (автоматической
переменной) статический и динамический тип совпадают.
По умолчанию объектная модель C++ работает с невиртуальными
методами. Механизм виртуальных функций работает только в
случае косвенной адресации (по указателю или ссылке).
36
Виртуальные функции (2 / 2)
Значения формальных параметров виртуальных функций
определяются (а) на этапе компиляции (б) типом объекта,
через который осуществляется вызов.
Отмена действия механизма виртуализации возможна и
достигается статическим вызовом метода при помощи
операции разрешения области видимости (::).
class Alpha {
/* … */
virtual void display();
};
class Beta : public Alpha {
void display();
}
37
Чистые виртуальные функции
Класс, где виртуальный метод объявляется впервые, должен
определять его тело либо декларировать метод как не имеющую
собственной реализации чистую виртуальную функцию.
Производный класс может наследовать реализацию
виртуального метода из базового класса или перекрывать его
собственной реализацией, при этом прототипы обеих реализаций
обязаны совпадать.
virtual void display() = 0;

Примечание: единственное исключение С++ делает для
возвращаемого значения. Значение, возвращаемое реализацией
в производном классе, может иметь тип, открыто наследующий
классу значения, возвращаемого реализацией в базовом.
38
Абстрактные классы
Класс, который определяет или наследует хотя бы одну чистую
виртуальную функцию, является абстрактным.
Экземпляры абстрактных классов создавать нельзя. Абстрактный
класс может реализовываться только как подобъект
производного, неабстрактного класса.
Чистые виртуальные функции могут иметь тело, вызов которого,
впрочем, может производиться только статически (при помощи
операции разрешения области видимости), но не динамически (при
помощи механизма виртуализации).

39
Чистые виртуальные функции
и абстрактные классы: пример
// class Alpha;
// class Beta : public Alpha;
virtual void Alpha::display() { /* … */ };
void Beta::display()
{
// вызов чистой виртуальной функции
Alpha::display();
/* … */
}

40
Множественное наследование
Множественное наследование в ООП — это наследование от
двух и более базовых классов, возможно, с различным уровнем
доступа. Язык C++ не накладывает ограничений на количество
базовых классов.
При множественном наследовании конструкторы базовых классов
вызываются в порядке перечисления имен классов в списке
базовых классов. Порядок вызова деструкторов ему прямо
противоположен.
Унаследованные от разных базовых классов методы не образуют
множество перегруженных функций, а потому разрешаются
только по имени, без учета их сигнатур.
41
Виртуальное наследование
При множественном наследовании возможна ситуация
неоднократного включения подобъекта одного и того же базового
класса в состав производного. Связанные с нею проблемы и
неоднозначности снимает виртуальное наследование.
Суть виртуального наследования — включение в состав класса
единственного разделяемого подобъекта базового класса
(виртуального базового класса).
Виртуальное наследование не характеризует базовый класс, а
лишь описывает его отношение к производному.

Использование виртуального наследования должно быть
взвешенным проектным решением конкретных проблем объектноориентированного проектирования.
42
Конструкция объектов при виртуальном
наследовании
Виртуальные базовые классы конструируются перед
невиртуальными независимо от их расположения в иерархии
наследования.
Ответственность за инициализацию виртуального базового класса
несет на себе ближайший (финальный) производный класс.
В промежуточных производных классах прямые вызовы
конструкторов виртуальных базовых классов автоматически
подавляются.

43
Множественное и виртуальное
наследование: пример
// множественное наследование
class Alpha
{ /* … */ };
class Beta : public Alpha
{ /* … */ };
class Gamma
{ /* … */ };
class Delta : public Beta, public Gamma
{ /* … */ };
// виртуальное наследование
class Alpha
{ /* … */ };
class Beta : virtual public Alpha
// то же: class Beta : public virtual Alpha
{ /* … */ };

44
Динамическая идентификация
типов времени выполнения (RTTI)
Динамическая идентификация типов времени выполнения (англ.
Real-Time Type Identification) обеспечивает специальную
поддержку полиморфизма и позволяет программе узнать
реальный производный тип объекта, адресуемого по ссылке или
по указателю на базовый класс. Поддержка RTTI в C++
реализована двумя операциями:
 операция dynamic_cast поддерживает преобразование типов времени
выполнения;
 операция typeid идентифицирует реальный тип выражения.

Операции RTTI — это события времени выполнения для классов с
виртуальными функциями и события времени компиляции для
остальных типов. Исследование RTTI-информации полезно, в
частности, при решении задач системного программирования.
45
Операция dynamic_cast
Встроенная унарная операция dynamic_cast языка C++ позволяет:
 безопасно трансформировать указатель на базовый класс в указатель
на производный класс (с возвратом нулевого указателя при
невозможности выполнения трансформации);
 преобразовывать леводопустимые значения, ссылающиеся на базовый
класс, в ссылки на производный класс (с возбуждением исключения
bad_cast при ошибке).

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

46
Операция dynamic_cast:
пример (указатели)
// классы Alpha и Beta образуют полиморфную
// иерархию, в которой класс Beta открыто
// наследует классу Alpha

Alpha *al

= new Beta;

if(Beta *bt = dynamic_cast<Beta*>(al)) {
/* успешно */
}
else {
/* неуспешно */
}

47
Операция dynamic_cast:
пример (ссылки)
// классы Alpha и Beta образуют полиморфную
// иерархию, в которой класс Beta открыто
// наследует классу Alpha

#include <typeinfo> // для std::bad_cast
void foo(Alpha &al)
{
/* … */
try {
Beta &bt = dynamic_cast<Beta&>(al))
}
catch(std::bad_cast) {
/* … */
}
}

48
Операция typeid (1 / 2)
Встроенная унарная операция typeid:
 позволяет установить фактический тип выражения-операнда;
 может использоваться с выражениями и именами любых типов (включая
выражения встроенных типов и константы).

Если операнд typeid принадлежит типу класса с одной и более
виртуальными функциями (не указателю на него!), результат
typeid может не совпадать с типом самого выражения.
Операция typeid имеет тип (возвращает значение типа) type_info
и требует подключения заголовочного файла <typeinfo>.

49
Операция typeid (2 / 2)
Реализация класса type_info зависит от компилятора, но в
общем и целом позволяет получить результат в виде
неизменяемой C-строки (const char*), присваивать объекты
type_info друг другу (operator =), а также сравнивать их на
равенство и неравенство (operator ==, operator !=).
#include <typeinfo> // для type_info
Alpha *al = new Alpha;

if(typeid(al) == typeid(Alpha*)) /* … */
if(typeid(*al) == typeid(Alpha)) /* … */

50
Вопросы производительности
 Глубина цепочки наследования не увеличивает затраты
времени и не ограничивает доступ к унаследованным членам
базовых классов.
 Вызов виртуальной функции в большинстве случаев не менее
эффективен, чем косвенный вызов функции по указателю на
нее.
 При использовании встроенных конструкторов глубина
иерархии наследования почти не влияет на
производительность.

 Отмена действия механизма виртуализации, как правило,
необходима по соображениям повышения эффективности.
51
Практикум №4

Постановка задачи
 Реализовать UML-модель как полиморфную иерархию классов
с шаблоном (специализированным шаблоном) класса.
 Дополнить результат иерархией классов исключительных
ситуаций, смоделировать каждую ситуацию и обеспечить ее
корректную обработку.
 Цель — перевести архитектурное описание проекта на языке
UML в исходный программный код на языке C++, отвечающий
заданным критериям полноты и качества результата.

52
Спасибо за внимание
Алексей Петров

More Related Content

What's hot (20)

PDF
C++ весна 2014 лекция 2
Technopark
 
PDF
C++ осень 2012 лекция 6
Technopark
 
PPTX
Java Core. Lecture# 3. Part# 3. Multithreading.
Anton Moiseenko
 
PDF
C# Desktop. Занятие 02.
Igor Shkulipa
 
PPT
Java. Методы
Unguryan Vitaliy
 
PDF
C++ Базовый. Занятие 02.
Igor Shkulipa
 
PDF
C# Desktop. Занятие 01.
Igor Shkulipa
 
PPTX
Java Core. Lecture# 3. Part# 2. Exceptions.
Anton Moiseenko
 
PPTX
Java Core. Lecture# 5. Concurrency.
Anton Moiseenko
 
PDF
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
Dima Dzuba
 
PDF
C++ STL & Qt. Занятие 03.
Igor Shkulipa
 
PDF
C# Desktop. Занятие 04.
Igor Shkulipa
 
PDF
Объектно-ориентированное программирование. Лекция 7 и 8.
Dima Dzuba
 
PPTX
Java Core. Lecture# 3. Part# 1. Abstract classes.
Anton Moiseenko
 
PDF
C++ Базовый. Занятие 04.
Igor Shkulipa
 
PDF
Объектно-Ориентированное Программирование на C++, Лекции 3 и 4
Dima Dzuba
 
PDF
C++ STL & Qt. Занятие 10.
Igor Shkulipa
 
PDF
Автоматическая генерация тестов по комментариям к программному коду
Alexey Noskov
 
PDF
C++ STL & Qt. Занятие 09.
Igor Shkulipa
 
PDF
C++ STL & Qt. Занятие 11.
Igor Shkulipa
 
C++ весна 2014 лекция 2
Technopark
 
C++ осень 2012 лекция 6
Technopark
 
Java Core. Lecture# 3. Part# 3. Multithreading.
Anton Moiseenko
 
C# Desktop. Занятие 02.
Igor Shkulipa
 
Java. Методы
Unguryan Vitaliy
 
C++ Базовый. Занятие 02.
Igor Shkulipa
 
C# Desktop. Занятие 01.
Igor Shkulipa
 
Java Core. Lecture# 3. Part# 2. Exceptions.
Anton Moiseenko
 
Java Core. Lecture# 5. Concurrency.
Anton Moiseenko
 
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
Dima Dzuba
 
C++ STL & Qt. Занятие 03.
Igor Shkulipa
 
C# Desktop. Занятие 04.
Igor Shkulipa
 
Объектно-ориентированное программирование. Лекция 7 и 8.
Dima Dzuba
 
Java Core. Lecture# 3. Part# 1. Abstract classes.
Anton Moiseenko
 
C++ Базовый. Занятие 04.
Igor Shkulipa
 
Объектно-Ориентированное Программирование на C++, Лекции 3 и 4
Dima Dzuba
 
C++ STL & Qt. Занятие 10.
Igor Shkulipa
 
Автоматическая генерация тестов по комментариям к программному коду
Alexey Noskov
 
C++ STL & Qt. Занятие 09.
Igor Shkulipa
 
C++ STL & Qt. Занятие 11.
Igor Shkulipa
 

Viewers also liked (6)

PDF
C++ осень 2012 лекция 9
Technopark
 
PDF
Черневич Алексей - Обучение языку С++
Aliaksei Charnevich
 
PDF
Обзор библиотеки Boost
MSU GML VideoGroup
 
PDF
C++ осень 2012 лекция 5
Technopark
 
PDF
C++ осень 2013 лекция 1
Technopark
 
PDF
Лекция 11. Вычислительная модель Pregel
Technopark
 
C++ осень 2012 лекция 9
Technopark
 
Черневич Алексей - Обучение языку С++
Aliaksei Charnevich
 
Обзор библиотеки Boost
MSU GML VideoGroup
 
C++ осень 2012 лекция 5
Technopark
 
C++ осень 2013 лекция 1
Technopark
 
Лекция 11. Вычислительная модель Pregel
Technopark
 
Ad

Similar to C++ осень 2013 лекция 4 (20)

PDF
C++ осень 2012 лекция 2
Technopark
 
PDF
C++ Базовый. Занятие 09.
Igor Shkulipa
 
PDF
C++ Базовый. Занятие 11.
Igor Shkulipa
 
PPT
Lecture 5
Anastasia Snegina
 
PPT
Lecture 8
Anastasia Snegina
 
PPTX
особенности программирования на с++
mcroitor
 
PDF
Дмитрий Прокопцев — R-ссылки в С++11
Yandex
 
PDF
C++ for real_programmers
daemon025
 
PDF
книга с++
Serghei Urban
 
PDF
C++ осень 2012 лекция 1
Technopark
 
PPTX
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Yandex
 
PPTX
СИ++ УМЕР. ДА ЗДРАВСТВУЕТ СИ++
Pavel Tsukanov
 
PPT
Lecture 3
Anastasia Snegina
 
PDF
Cpp0x Introduction
Fedor Vompe
 
PPTX
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Yandex
 
PDF
углубленное программирование на C++. лекция no.5 [4.0]
Technopark
 
PDF
C++ осень 2012 лекция 3
Technopark
 
PDF
C++ Базовый. Занятие 08.
Igor Shkulipa
 
PPTX
C++ CoreHard Autumn 2018. Кодогенерация C++ кроссплатформенно. Продолжение - ...
corehard_by
 
PDF
C++ Базовый. Занятие 10.
Igor Shkulipa
 
C++ осень 2012 лекция 2
Technopark
 
C++ Базовый. Занятие 09.
Igor Shkulipa
 
C++ Базовый. Занятие 11.
Igor Shkulipa
 
особенности программирования на с++
mcroitor
 
Дмитрий Прокопцев — R-ссылки в С++11
Yandex
 
C++ for real_programmers
daemon025
 
книга с++
Serghei Urban
 
C++ осень 2012 лекция 1
Technopark
 
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Yandex
 
СИ++ УМЕР. ДА ЗДРАВСТВУЕТ СИ++
Pavel Tsukanov
 
Cpp0x Introduction
Fedor Vompe
 
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Yandex
 
углубленное программирование на C++. лекция no.5 [4.0]
Technopark
 
C++ осень 2012 лекция 3
Technopark
 
C++ Базовый. Занятие 08.
Igor Shkulipa
 
C++ CoreHard Autumn 2018. Кодогенерация C++ кроссплатформенно. Продолжение - ...
corehard_by
 
C++ Базовый. Занятие 10.
Igor Shkulipa
 
Ad

More from Technopark (20)

PDF
Лекция 14. Hadoop в Поиске Mail.Ru
Technopark
 
PDF
Лекция 13. YARN
Technopark
 
PDF
Лекция 12. Spark
Technopark
 
PDF
Лекция 10. Apache Mahout
Technopark
 
PDF
Лекция 9. ZooKeeper
Technopark
 
PDF
Лекция 7. Введение в Pig и Hive
Technopark
 
PDF
Лекция 6. MapReduce в Hadoop (графы)
Technopark
 
PDF
Лекция 5. MapReduce в Hadoop (алгоритмы)
Technopark
 
PDF
Лекция 4. MapReduce в Hadoop (введение)
Technopark
 
PDF
Лекция 3. Распределённая файловая система HDFS
Technopark
 
PDF
Лекция 2. Основы Hadoop
Technopark
 
PDF
Лекция 1. Введение в Big Data и MapReduce
Technopark
 
PPTX
СУБД 2013 Лекция №10 "Нереляционное решение в области баз данных — NoSQL"
Technopark
 
PPT
СУБД 2013 Лекция №10 "Нереляционное решение в области баз данных — NoSQL" Час...
Technopark
 
PPTX
СУБД 2013 Лекция №9 "Безопасность баз данных"
Technopark
 
PPTX
СУБД 2013 Лекция №8 "Конфигурирование базы данных"
Technopark
 
PPTX
СУБД 2013 Лекция №7 "Оптимизация запросов и индексирование"
Technopark
 
PPTX
СУБД 2013 Лекция №5 "Определение узких мест"
Technopark
 
PPTX
СУБД 2013 Лекция №6 "Профилирование запросов. Сложноструктурированные SQL-зап...
Technopark
 
PPTX
СУБД 2013 Лекция №4 "Расширенные возможности работы с базами данных. Триггеры...
Technopark
 
Лекция 14. Hadoop в Поиске Mail.Ru
Technopark
 
Лекция 13. YARN
Technopark
 
Лекция 12. Spark
Technopark
 
Лекция 10. Apache Mahout
Technopark
 
Лекция 9. ZooKeeper
Technopark
 
Лекция 7. Введение в Pig и Hive
Technopark
 
Лекция 6. MapReduce в Hadoop (графы)
Technopark
 
Лекция 5. MapReduce в Hadoop (алгоритмы)
Technopark
 
Лекция 4. MapReduce в Hadoop (введение)
Technopark
 
Лекция 3. Распределённая файловая система HDFS
Technopark
 
Лекция 2. Основы Hadoop
Technopark
 
Лекция 1. Введение в Big Data и MapReduce
Technopark
 
СУБД 2013 Лекция №10 "Нереляционное решение в области баз данных — NoSQL"
Technopark
 
СУБД 2013 Лекция №10 "Нереляционное решение в области баз данных — NoSQL" Час...
Technopark
 
СУБД 2013 Лекция №9 "Безопасность баз данных"
Technopark
 
СУБД 2013 Лекция №8 "Конфигурирование базы данных"
Technopark
 
СУБД 2013 Лекция №7 "Оптимизация запросов и индексирование"
Technopark
 
СУБД 2013 Лекция №5 "Определение узких мест"
Technopark
 
СУБД 2013 Лекция №6 "Профилирование запросов. Сложноструктурированные SQL-зап...
Technopark
 
СУБД 2013 Лекция №4 "Расширенные возможности работы с базами данных. Триггеры...
Technopark
 

Recently uploaded (6)

PPTX
Saint Katherine Tekakwitha, Lily of the Mohawks (Russian).pptx
Martin M Flynn
 
PPTX
Математические и статистические методы обработки научных данных..pptx
loganesan43
 
PPTX
Saint Henry II, German King and Holy Roman Emperor (972-1024) - Russian.pptx
Martin M Flynn
 
PPTX
Saint Camillus de Lellis, (1550 –1614) founder of the Camillians (Russian).pptx
Martin M Flynn
 
PPT
Истинная модель парной линейной регрессии
loganesan43
 
PPT
Детский воспитательный коллектив как средство воспитания
eapermiakov
 
Saint Katherine Tekakwitha, Lily of the Mohawks (Russian).pptx
Martin M Flynn
 
Математические и статистические методы обработки научных данных..pptx
loganesan43
 
Saint Henry II, German King and Holy Roman Emperor (972-1024) - Russian.pptx
Martin M Flynn
 
Saint Camillus de Lellis, (1550 –1614) founder of the Camillians (Russian).pptx
Martin M Flynn
 
Истинная модель парной линейной регрессии
loganesan43
 
Детский воспитательный коллектив как средство воспитания
eapermiakov
 

C++ осень 2013 лекция 4

  • 2. Лекция №4. Специальные вопросы наследования и полиморфизма 1. Инициализация, копирование, преобразование и уничтожение объектов (продолжение). Идиома RAII. 2. Раннее и позднее связывание. Перегрузка и перекрытие членов класса. 3. Виртуальные функции. Абстрактные классы. Множественное и виртуальное наследование. 4. RTTI и операции приведения типов. Производительность и безопасность полиморфизма и средств поддержки RTTI. 5. Постановка задач к практикуму №4. 2
  • 3. Инициализация без конструктора (1 / 2) Класс, все члены которого открыты, может задействовать механизм явной позиционной инициализации, ассоциирующий значения в списке инициализации с членами данных в соответствии с их порядком. class Test { public: int int_prm; double dbl_prm; string str_prm; }; // … Test test = { 1, -3.14, "dictum factum" }; 3
  • 4. Инициализация без конструктора (2 / 2) Преимуществами такой техники выступают:  скорость и эффективность, особо значимые при выполнении во время запуска программы (для глобальных объектов). Недостатками инициализации без конструктора являются:  пригодность только для классов, члены которых открыты;  отсутствие поддержки инкапсуляции и абстрактных типов;  требование предельной точности и аккуратности в применении. 4
  • 5. Конструкторы по умолчанию (1 / 2) Конструктор по умолчанию не требует задания значений его параметров, хотя таковые могут присутствовать в сигнатуре. class Test { public: Test(int ipr = 0, double dpr = 0.0); /* … */ }; Наличие формальных параметров в конструкторе по умолчанию позволяет сократить общее число конструкторов и объем исходного кода. 5
  • 6. Конструкторы по умолчанию (2 / 2) Если в классе определен хотя бы один конструктор с параметрами, то при использовании класса со стандартными контейнерами и динамическими массивами экземпляров конструктор по умолчанию обязателен. Test *tests = new Test[TEST_PLAN_SIZE]; Если конструктор по умолчанию не определен, но существует хотя бы один конструктор с параметрами, в определении объектов должны присутствовать аргументы. Если ни одного конструктора не определено, объект класса не инициализируется (память под статическими объектами по общим правилам обнуляется). 6
  • 7. Конструкторы с параметрами: пример class Test { public: Test(int prm) : _prm (prm) {} private: int _prm; }; // все вызовы конструктора допустимы и эквивалентны Test test1(10), test2 = Test(10), test3 = 10; // для одного аргумента 7
  • 8. Массивы объектов: пример // массивы объектов класса определяются // аналогично массивам объектов базовых типов // для конструктора с одним аргументом Test testplan1[] = { 10, -5, 0, 127 }; // для конструктора с несколькими аргументами Test testplan2[5] = { Test(10, 0.1), Test(-5, -3.6), Test(0, 0.0), Test() // если есть конструктор по умолчанию }; 8
  • 9. Закрытые и защищенные конструкторы Описание конструктора класса как защищенного или закрытого дает возможность ограничить или полностью запретить отдельные способы создания объектов класса. В большинстве случаев закрытые и защищенные конструкторы используются для:  предотвращения копирования одного объекта в другой;  указания на то, что конструктор должен вызываться только для создания подобъектов базового класса в объекте производного класса, а не создания объектов, непосредственно доступных в коде программы. 9
  • 10. Почленная инициализация и присваивание (1 / 2) Почленная инициализация по умолчанию — механизм инициализации одного объекта класса другим объектом того же класса, который активизируется независимо от наличия в определении класса явного конструктора. Почленная инициализация по умолчанию происходит в следующих ситуациях:  явная инициализация одного объекта другим;  передача объекта класса в качестве аргумента функции;  передача объекта класса в качестве возвращаемого функцией значения;  определение непустого стандартного последовательного контейнера;  вставка объекта класса в стандартный контейнер. 10
  • 11. Почленная инициализация и присваивание (2 / 2) Почленная инициализация по умолчанию подавляется при наличии в определении класса конструктора копирования. Запрет почленной инициализации по умолчанию осуществляется одним из следующих способов:  описание закрытого конструктора копирования (не действует для методов класса и дружественных объектов);  описание конструктора копирования без его определения (действует всюду). Почленное присваивание по умолчанию — механизм присваивания одному объекту класса значения другого объекта того же класса, отличный от почленной инициализации по умолчанию использованием копирующей операции-функции присваивания вместо конструктора копирования. 11
  • 12. Конструкторы копирования Конструктор копирования принимает в качестве единственного параметра константную ссылку на существующий объект класса. В случае отсутствия явного конструктора копирования в определении класса производится почленная инициализация объекта по умолчанию. class Test { /* … */ Test(const Test &other); /* … */ }; 12
  • 13. Конструкторы и операции преобразования Конструкторы преобразования служат для построения объектов класса по одному или нескольким значениям иных типов. Операции преобразования позволяют преобразовывать содержимое объектов класса к требуемым типам данных. class Test { // конструкторы преобразования Test(const char *); Test(const string &); // операции преобразования operator int () { return int_prm; } operator double () { return dbl_prm; } /* … */ }; 13
  • 14. Деструкторы. Виртуальные деструкторы (1 / 2) Деструктор — не принимающий параметров и не возвращающий результат метод класса, автоматически вызываемый при выходе объекта из области видимости и применении к указателю на объект класса операции delete. class Test { /* … */ virtual ~Test(); }; Примечание: деструктор не вызывается при выходе из области видимости ссылки или указателя на объект. 14
  • 15. Деструкторы. Виртуальные деструкторы (2 / 2) Типичные задачи деструктора:  сброс содержимого программных буферов в долговременные хранилища;  освобождение (возврат) системных ресурсов, главным образом — оперативной памяти;  закрытие файлов или устройств;  снятие блокировок, останов таймеров и т.д. Для обеспечения корректного освобождения ресурсов объектами производных классов деструкторы в иерархиях, как правило, определяют как виртуальные. 15
  • 16. Идиома RAII Закрепление за конструкторами функции захвата, выделения, блокировки или инициализации ресурсов, а за деструкторами — функции их возврата, освобождения и снятия установленных блокировок:  позволяет безопасно обрабатывать ошибки и исключения;  составляет суть одной из важнейших идиом ОО-программирования RAII (англ. Resource Acquisition Is Initialization — «получение ресурса есть инициализация»). Работа идиомы RAII в языке C++ основана, главным образом, на гарантированном вызове деструкторов автоматических переменных, являющихся экземплярами классов, при выходе из соответствующих областей видимости. 16
  • 17. Явный вызов деструкторов Потребность в явном вызове деструктора обычно связана с необходимостью уничтожить динамически размещенный объект без освобождения памяти. char *buf = new char[sizeof(Test)]; // "размещающий" вариант new Test *ptc = new (buf) Test(100); /* … */ ptc->~Test(); // вызов 1 Test *ptc = new (buf) Test(200); /* … */ ptc->~Test(); // вызов 2 delete [] buf; 17
  • 18. Список инициализации в конструкторе Выполнение любого конструктора состоит из двух фаз:  фаза явной (неявной) инициализации (обработка списка инициализации) — предполагает начальную инициализацию членов данных;  фаза вычислений (исполнение тела конструктора) — предполагает присваивание значений (в предварительно инициализированных областях памяти). Присваивание значений членам данных – объектам классов в теле конструктора неэффективно ввиду ранее произведенной инициализации по умолчанию. Присваивание значений членам данных, представляющих базовые типы, по эффективности равнозначно инициализации. К началу исполнения тела конструктора все константные члены и члены-ссылки должны быть инициализированы. 18
  • 19. Семантика переноса (C++11) Введение в С++11 семантики переноса (англ. move semantics) обогащает язык возможностями более тонкого и эффективного управления памятью данных, устраняющего копирование объектов там, где оно нецелесообразно. Технически семантика переноса реализуется при помощи ссылок на праводопустимые выражения и конструкторов переноса. Конструкторы переноса не создают точную копию своего параметра, а перенастраивают параметр так, чтобы права владения соответствующей областью памяти были переданы вновь создаваемому объекту («заимствованы» последним). Аналогично работают операции присваивания с переносом. 19
  • 20. Конструктор переноса: пример (1 / 2) class Alpha { public: Alpha(); Alpha(const Alpha &a); // конструктор копирования Alpha(Alpha &&a); // конструктор переноса ~Alpha(); private: size_t sz; double *d; }; Alpha::Alpha() : sz(0), d(0) { } Alpha::~Alpha() { delete [] d; } 20
  • 21. Конструктор переноса: пример (2 / 2) // конструктор копирования Alpha::Alpha(const Alpha &a) : sz(a.sz) { d = new double[sz]; /* … */ for(size_t i = 0; i < sz; i++) d[i] = a.d[i]; } // конструктор переноса Alpha::Alpha(Alpha &&a) : sz(a.sz) { d = a.d; // перенастройка параметра a.d = nullptr; // C++11 a.sz = 0; } 21
  • 22. Наследование: ключевые понятия Наследование содействует повторному использованию атрибутов и методов класса, а значит, делает процесс разработки ПО более эффективным. Возникающие между классами A и B отношения наследования позволяют, например, говорить, что:  класс A является базовым (родительским) классом, классом-предком, надклассом (англ. superclass);  класс B является производным (дочерним) классом, классом-потомком, подклассом (англ. subclass). Отношения наследования связывают классы в иерархию наследования, вид которой зависит от числа базовых классов у каждого производного:  при одиночном наследовании иерархия имеет вид дерева;  при множественном наследовании — вид направленного ациклического графа (НАГ) произвольного вида. 22
  • 23. Наследование: примеры (UML) Одиночное наследование (слева), множественное наследование (в центре, классы Alpha и Alpha (II) идентичны), виртуальное множественное наследование (справа) 23
  • 24. Полиморфизм подклассов В том случае если базовый и производный классы имеют общий открытый интерфейс, говорят, что производный класс представляет собой подкласс базового. Отношение между классом и подклассом, позволяющее указателю или ссылке на базовый класс без вмешательства программиста адресовать объект производного класса, возникает в C++ благодаря поддержке полиморфизма. Полиморфизм позволяет предложить такую реализацию ядра объектно-ориентированного приложения, которая не будет зависеть от конкретных используемых подклассов. 24
  • 25. Раннее и позднее связывание В рамках классического объектного подхода, — а равно и процедурного программирования, — адрес вызываемой функции (метода класса) определяется на этапе компиляции (сборки). Такой порядок связывания вызова функции и ее адреса получил название раннего (статического). Позднее (динамическое) связывание состоит в нахождении (разрешении) нужной функции во время исполнения кода. При этом работа по разрешению типов перекладывается с программиста на компилятор. В языке C++ динамическое связывание поддерживается механизмом виртуальных методов класса, для работы с которыми компиляторы строят таблицы виртуальных методов (англ. VMT, virtual method table). 25
  • 26. Базовые и производные классы ОО-проектирование допускает существование классов, которые могут выполнять чисто технические функции, моделировать абстрактные сущности и отличаться функциональной неполнотой: • не подлежащий реализации в виде экземпляров (объектов) базовый класс может оставаться абстрактным. В противовес абстрактным базовым классам классы, предполагающие создание экземпляров, именуют конкретными; • (абстрактные) базовые классы специфицируют открытые интерфейсы иерархий и содержат общие для всех подклассов атрибуты и методы (или их прототипы). Множество подклассов любого базового класса ограничено иерархией наследования, но потенциально бесконечно (ввиду отсутствия пределов по расширению иерархии вглубь и вширь). 26
  • 27. Определение наследования Определение отношения наследования имеет вид (для одиночного наследования): // заголовок класса class <имя производного класса> : <уровень доступа> <имя базового класса> // тело класса { /* … */ }; где <уровень доступа> — ключевое слово public, private или protected, а <имя базового класса> — имя ранее определенного (не описанного!) класса. В зависимости от уровня доступа к членам базового класса говорят об открытом, закрытом или защищенном наследовании. 27
  • 28. Определение наследования: пример // описание производного класса // (не включает список базовых классов!) class Deposit; /* … */ // определения классов class Account { /* … */ }; class Deposit : public Account { /* … */ }; 28
  • 29. Защищенные и закрытые члены класса Атрибуты и методы базового класса, как правило, должны быть непосредственно доступны для производных классов и непосредственно недоступны для прочих компонентов программы. В этом случае они помещаются в секцию protected, в результате чего защищенные члены данных и методы базового класса:  доступны производному классу (прямому потомку);  недоступны классам вне рассматриваемой иерархии, глобальным функциям и вызывающей программе. Если наличие прямого доступа к члену класса со стороны производных классов нежелательно, он вводится как закрытый. Закрытые члены класса не наследуются потомками. Для доступа к ним класс-потомок должен быть объявлен в классе-предке как дружественный. Отношения дружественности не наследуются. 29
  • 30. Перегрузка и перекрытие членов класса Члены данных базового класса могут перекрываться одноименными членами данных производного класса, при этом их типы не должны обязательно совпадать. (Для доступа к члену базового класса его имя должно быть квалифицировано.) Методы базового и производного классов не образуют множество перегруженных функций. В этом случае методы производного класса не перегружают (англ. overload), а перекрывают (англ. override) методы базового. Для явного создания объединенного множества перегруженных функций базового и производного классов используется объявление using, которое вводит именованный член базового класса в область видимости производного. 30
  • 31. Перегрузка и перекрытие членов класса: пример Примечание: в область видимости производного класса попадают все одноименные методы базового класса, а не только некоторые из них. class Account { /* … */ void display(const char *fmt); void display(const int mode = 0); }; class Deposit : public Account { void display(const string &fmt); using Account::display; /* … */ }; 31
  • 32. Порядок вызова конструкторов производных классов (1 / 2) Порядок вызова конструкторов объектов-членов, а также базовых классов при построении объекта производного класса не зависит от порядка их перечисления в списке инициализации конструктора производного класса и является следующим:  конструктор базового класса (если таковых несколько, конструкторы вызываются в порядке перечисления имен классов в списке базовых классов);  конструктор объекта-члена (если таковых несколько, конструкторы вызываются в порядке объявления членов данных в определении класса);  конструктор производного класса. 32
  • 33. Порядок вызова конструкторов производных классов (2 / 2) Конструктор производного класса может вызывать конструкторы классов, непосредственно являющихся базовыми для данного (прямых предков), и — без учета виртуального наследования — только их. Примечание: правильно спроектированный конструктор производного класса не должен инициализировать атрибуты базового класса напрямую (путем присваивания значений). 33
  • 34. Список инициализации при наследовании: пример class Alpha { public: // Alpha(); Alpha(int i); /* … */ }; class Beta : public Alpha { public: Beta() : _s("dictum factum") { } // Beta() : Alpha(), _s("dictum factum") { } Beta(int i, string s) : Alpha(i), _s(s) { } protected: string _s; /* … */ }; 34
  • 35. Порядок вызова деструкторов производных классов Порядок вызова деструкторов при уничтожении объекта производного класса прямо противоположен порядку вызова конструкторов и является следующим:  деструктор производного класса;  деструктор объекта-члена (или нескольких);  деструктор базового класса (или нескольких). Взаимная противоположность порядка вызова конструкторов и деструкторов является строгой гарантией языка C++. 35
  • 36. Виртуальные функции (1 / 2) Методы, результат разрешения вызова которых зависит от «реального» (динамического) типа объекта, доступного по указателю или ссылке, называются виртуальными и при определении в базовом классе снабжаются спецификатором virtual. Примечание: в этом контексте тип непосредственно определяемого экземпляра, ссылки или указателя на объект называется статическим. Для самого объекта любого типа (автоматической переменной) статический и динамический тип совпадают. По умолчанию объектная модель C++ работает с невиртуальными методами. Механизм виртуальных функций работает только в случае косвенной адресации (по указателю или ссылке). 36
  • 37. Виртуальные функции (2 / 2) Значения формальных параметров виртуальных функций определяются (а) на этапе компиляции (б) типом объекта, через который осуществляется вызов. Отмена действия механизма виртуализации возможна и достигается статическим вызовом метода при помощи операции разрешения области видимости (::). class Alpha { /* … */ virtual void display(); }; class Beta : public Alpha { void display(); } 37
  • 38. Чистые виртуальные функции Класс, где виртуальный метод объявляется впервые, должен определять его тело либо декларировать метод как не имеющую собственной реализации чистую виртуальную функцию. Производный класс может наследовать реализацию виртуального метода из базового класса или перекрывать его собственной реализацией, при этом прототипы обеих реализаций обязаны совпадать. virtual void display() = 0; Примечание: единственное исключение С++ делает для возвращаемого значения. Значение, возвращаемое реализацией в производном классе, может иметь тип, открыто наследующий классу значения, возвращаемого реализацией в базовом. 38
  • 39. Абстрактные классы Класс, который определяет или наследует хотя бы одну чистую виртуальную функцию, является абстрактным. Экземпляры абстрактных классов создавать нельзя. Абстрактный класс может реализовываться только как подобъект производного, неабстрактного класса. Чистые виртуальные функции могут иметь тело, вызов которого, впрочем, может производиться только статически (при помощи операции разрешения области видимости), но не динамически (при помощи механизма виртуализации). 39
  • 40. Чистые виртуальные функции и абстрактные классы: пример // class Alpha; // class Beta : public Alpha; virtual void Alpha::display() { /* … */ }; void Beta::display() { // вызов чистой виртуальной функции Alpha::display(); /* … */ } 40
  • 41. Множественное наследование Множественное наследование в ООП — это наследование от двух и более базовых классов, возможно, с различным уровнем доступа. Язык C++ не накладывает ограничений на количество базовых классов. При множественном наследовании конструкторы базовых классов вызываются в порядке перечисления имен классов в списке базовых классов. Порядок вызова деструкторов ему прямо противоположен. Унаследованные от разных базовых классов методы не образуют множество перегруженных функций, а потому разрешаются только по имени, без учета их сигнатур. 41
  • 42. Виртуальное наследование При множественном наследовании возможна ситуация неоднократного включения подобъекта одного и того же базового класса в состав производного. Связанные с нею проблемы и неоднозначности снимает виртуальное наследование. Суть виртуального наследования — включение в состав класса единственного разделяемого подобъекта базового класса (виртуального базового класса). Виртуальное наследование не характеризует базовый класс, а лишь описывает его отношение к производному. Использование виртуального наследования должно быть взвешенным проектным решением конкретных проблем объектноориентированного проектирования. 42
  • 43. Конструкция объектов при виртуальном наследовании Виртуальные базовые классы конструируются перед невиртуальными независимо от их расположения в иерархии наследования. Ответственность за инициализацию виртуального базового класса несет на себе ближайший (финальный) производный класс. В промежуточных производных классах прямые вызовы конструкторов виртуальных базовых классов автоматически подавляются. 43
  • 44. Множественное и виртуальное наследование: пример // множественное наследование class Alpha { /* … */ }; class Beta : public Alpha { /* … */ }; class Gamma { /* … */ }; class Delta : public Beta, public Gamma { /* … */ }; // виртуальное наследование class Alpha { /* … */ }; class Beta : virtual public Alpha // то же: class Beta : public virtual Alpha { /* … */ }; 44
  • 45. Динамическая идентификация типов времени выполнения (RTTI) Динамическая идентификация типов времени выполнения (англ. Real-Time Type Identification) обеспечивает специальную поддержку полиморфизма и позволяет программе узнать реальный производный тип объекта, адресуемого по ссылке или по указателю на базовый класс. Поддержка RTTI в C++ реализована двумя операциями:  операция dynamic_cast поддерживает преобразование типов времени выполнения;  операция typeid идентифицирует реальный тип выражения. Операции RTTI — это события времени выполнения для классов с виртуальными функциями и события времени компиляции для остальных типов. Исследование RTTI-информации полезно, в частности, при решении задач системного программирования. 45
  • 46. Операция dynamic_cast Встроенная унарная операция dynamic_cast языка C++ позволяет:  безопасно трансформировать указатель на базовый класс в указатель на производный класс (с возвратом нулевого указателя при невозможности выполнения трансформации);  преобразовывать леводопустимые значения, ссылающиеся на базовый класс, в ссылки на производный класс (с возбуждением исключения bad_cast при ошибке). Единственным операндом dynamic_cast должен являться тип класса, в котором имеется хотя бы один виртуальный метод. 46
  • 47. Операция dynamic_cast: пример (указатели) // классы Alpha и Beta образуют полиморфную // иерархию, в которой класс Beta открыто // наследует классу Alpha Alpha *al = new Beta; if(Beta *bt = dynamic_cast<Beta*>(al)) { /* успешно */ } else { /* неуспешно */ } 47
  • 48. Операция dynamic_cast: пример (ссылки) // классы Alpha и Beta образуют полиморфную // иерархию, в которой класс Beta открыто // наследует классу Alpha #include <typeinfo> // для std::bad_cast void foo(Alpha &al) { /* … */ try { Beta &bt = dynamic_cast<Beta&>(al)) } catch(std::bad_cast) { /* … */ } } 48
  • 49. Операция typeid (1 / 2) Встроенная унарная операция typeid:  позволяет установить фактический тип выражения-операнда;  может использоваться с выражениями и именами любых типов (включая выражения встроенных типов и константы). Если операнд typeid принадлежит типу класса с одной и более виртуальными функциями (не указателю на него!), результат typeid может не совпадать с типом самого выражения. Операция typeid имеет тип (возвращает значение типа) type_info и требует подключения заголовочного файла <typeinfo>. 49
  • 50. Операция typeid (2 / 2) Реализация класса type_info зависит от компилятора, но в общем и целом позволяет получить результат в виде неизменяемой C-строки (const char*), присваивать объекты type_info друг другу (operator =), а также сравнивать их на равенство и неравенство (operator ==, operator !=). #include <typeinfo> // для type_info Alpha *al = new Alpha; if(typeid(al) == typeid(Alpha*)) /* … */ if(typeid(*al) == typeid(Alpha)) /* … */ 50
  • 51. Вопросы производительности  Глубина цепочки наследования не увеличивает затраты времени и не ограничивает доступ к унаследованным членам базовых классов.  Вызов виртуальной функции в большинстве случаев не менее эффективен, чем косвенный вызов функции по указателю на нее.  При использовании встроенных конструкторов глубина иерархии наследования почти не влияет на производительность.  Отмена действия механизма виртуализации, как правило, необходима по соображениям повышения эффективности. 51
  • 52. Практикум №4 Постановка задачи  Реализовать UML-модель как полиморфную иерархию классов с шаблоном (специализированным шаблоном) класса.  Дополнить результат иерархией классов исключительных ситуаций, смоделировать каждую ситуацию и обеспечить ее корректную обработку.  Цель — перевести архитектурное описание проекта на языке UML в исходный программный код на языке C++, отвечающий заданным критериям полноты и качества результата. 52