если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется

Чисто виртуальные функции и абстрактные типы

Когда виртуальная функция не переопределена в производном классе, то при вызове ее в объекте производного класса вызывается версия из базового класса. Однако во многих случаях невоз­можно ввести содержательное определение виртуальной функции в базовом классе. Например, при объявлении базового класса figure в предыдущем примере определение функции show_area() не несет никакого содержания. Она не вычисляет и не выводит на экран площадь объекта какого- либо типа. Имеется два способа действий в подобной ситуации. Первый, подобно предыдущему примеру, заключается в выводе какого-нибудь предупреждающего сообщения. Хотя такой подход полезен в некоторых ситуациях, он не является универсальным. Могут быть такие виртуальные функции, которые обязательно должны быть определены в производных классах, без чего эти производные классы не будут иметь никакого значения. Например, класс triangle бесполезен, если не определена функция show_агеа(). В таких случаях необходим метод, гарантирующий, что производные классы действительно определят все необходимые функции. Язык С++ предлагает в качестве решения этой проблемы чисто виртуальные функции.

Чисто виртуальная функция (pure virtual function) является функцией, которая объявляется в базовом классе, но не имеет в нем определения. Поскольку она не имеет определения, то есть
тела в этом базовом классе, то всякий производный класс обязан иметь свою собственную версию определения. Для объявления чисто виртуальной функции используется следующая общая форма:

virtual тип имя_функции(список параметров) = 0;

Здесь тип обозначает тип возвращаемого значения, а имя_функции является именем функции. На­пример, следующая версия функции show_area() класса figure является чисто виртуальной функцией.

class figure <
double х, у;
public:
void set_dim(double i, double j=0) <
x = i;
y = j;
>
virtual void show_area() = 0; // чисто виртуальная
>;

При введении чисто виртуальной функции в производном классе обязательно необходимо опре­делить свою собственную реализацию этой функции. Если класс не будет содержать определения этой функции, то компилятор выдаст ошибку. Например, если попытаться откомпилировать сле­дующую модифицированную версию программы figure, в которой удалено определение функции
snow_area() из класса circle, то будет выдано сообщение об ошибке:

/* Данная программа не компилируется, поскольку класс circle не переопределил show_агеа() */
#include
class figure <
protected:
double x, y;
public:
void set_dim(double i, double j) <
x = i;
у = j;
>
virtual void show_area() = 0; // pure
>;
class triangle: public figure <
public:
void show_area() <
cout set_dim(10.0, 5.0);
p->show_area ();
p = &s;
p->set_dim(10.0, 5.0);
p->show_area();
return 0;
>

Если какой-либо класс имеет хотя бы одну чисто виртуальную функцию, то такой класс называется абстрактным (abstract). Важной особенностью абстрактных классов является то, что не существует ни одного объекта данного класса. Вместо этого абстрактный класс служит в качестве базового для других производных классов. Причина, по которой абстрактный класс не может быть ис­пользован для объявления объекта, заключается в том, что одна или несколько его функций-членов не имеют определения. Тем не менее, даже если базовый класс является абстрактным, все равно можно объявлять указатели или ссылки на него, с помощью которых затем поддерживает­ся полиморфизм времени исполнения.

Источник

C++/Pure virtual function

Чисто виртуальная функция (pure virtual function, чисто виртуальный метод, pure virtual method) или Абстрактная функция (Abstract function, абстрактный метод, abstract method) — функция/метод, которая объявляется в базовом классе, но не может иметь в нём определения/тела, поэтому всякий производный класс обязан иметь свою собственную версию её определения/тела.

Содержание

Отличия виртуальной и чисто виртуальной (абстрактной) функций

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

Цель создания

Когда обычная виртуальная функция не переопределена в производном классе, то при вызове её в объекте производного класса вызывается версия из базового класса. Однако во многих случаях невоз­можно ввести содержательное определение виртуальной функции в базовом классе. Для этого и существует чисто виртуальная функция (абстрактная функция).

E.g., при объявлении базового класса для всех фигур Фигура определение функции площадь() не несет никакого содержания т.к. для каждой конкретной фигуры существует отдельная формула расчёта площади. Имеется два способа действий в подобной ситуации. Первый заключается в выводе какого-нибудь предупреждающего сообщения. Хотя такой подход полезен в некоторых ситуациях, он не является универсальным. Могут быть такие виртуальные функции, которые обязательно должны быть определены в производных классах, без чего эти производные классы не будут иметь никакого значения. В таких случаях необходим метод, гарантирующий, что производные классы действительно определят все необходимые функции. Язык С++ предлагает в качестве решения этой проблемы чисто виртуальные функции (абстрактные функции).

Синтаксис

Для объявления чисто виртуальной функции используется следующая общая форма:

Здесь тип обозначает тип возвращаемого значения, а имя_функции является именем функции.

Ограничения

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

Абстрактный класс

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

Примеры

Следующая версия функции show_area() класса СFigure является чисто виртуальной функцией:

Источник

Абстрактный класс. Чисто виртуальные функции

если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть картинку если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Картинка про если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть картинку если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Картинка про если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть картинку если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Картинка про если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть картинку если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Картинка про если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется

если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть картинку если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Картинка про если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется

Чисто виртуальная функция (pure virtual function) является функцией, которая объявляется в базовом классе, но не имеет в нем определения. Поскольку она не имеет определения, то есть
тела в этом базовом классе, то всякий производный класс обязан иметь свою собственную версию определения. Для объявления чисто виртуальной функции используется следующая общая форма:

virtual тип имя_функции(список параметров) = 0;

Здесь тип обозначает тип возвращаемого значения, а имя_функции является именем функции. На­пример, следующая версия функции show_area() класса figure является чисто виртуальной функцией.

class figure <
double х, у;
public:
void set_dim(double i, double j=0) <
x = i;
y = j;
>
virtual void show_area() = 0; // чисто виртуальная
>;

При введении чисто виртуальной функции в производном классе обязательно необходимо опре­делить свою собственную реализацию этой функции. Если класс не будет содержать определения этой функции, то компилятор выдаст ошибку. Например, если попытаться откомпилировать сле­дующую модифицированную версию программы figure, в которой удалено определение функции
snow_area() из класса circle, то будет выдано сообщение об ошибке:

/* Данная программа не компилируется, поскольку класс circle не переопределил show_агеа() */
#include
class figure <
protected:
double x, y;
public:
void set_dim(double i, double j) <
x = i;
у = j;
>
virtual void show_area() = 0; // pure
>;
class triangle: public figure <
public:
void show_area() <
cout set_dim(10.0, 5.0);
p->show_area ();
p = &s;
p->set_dim(10.0, 5.0);
p->show_area();
return 0;
>

Если какой-либо класс имеет хотя бы одну виртуальную функцию, то такой класс называется абстрактным (abstract). Важной особенностью абстрактных классов является то, что не существует ни одного объекта данного класса. Вместо этого абстрактный класс служит в качестве базового для других производных классов. Причина, по которой абстрактный класс не может быть ис­пользован для объявления объекта, заключается в том, что одна или несколько его функций-членов не имеют определения. Тем не менее, даже если базовый класс является абстрактным, все равно можно объявлять указатели или ссылки на него, с помощью которых затем поддерживает­ся полиморфизм времени исполнения.

35.Множественное наследование в С++. Прямая и косвенная база. Исключение дублирования членов в производных классах..

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

//Листинг 7. Пример множественного наследования

class C: public A, public B //наследуем класс С от A и B

Схема иерархии классов, определенных в последнем примере, изображена на рис.4

если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть картинку если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Картинка про если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется

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

class C: public B, public D <…>;

если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть картинку если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Картинка про если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется

Дублирование косвенного базового класса (рис 5а) приводит к включению в производный класс нескольких объектов базового класса. Для класса С в последнем примере это означает, что компонентное данное x будет существовать в объектах данного класса в двух экземплярах – один унаследован через класс В, другой – через класс D, что порождает проблему неоднозначности при доступе к дублирующимся компонентам класса: неясно, какой из одноименных компонент изменится при следующем обращении

если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть картинку если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Картинка про если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется

Попытка доступа к члену данных x для объекта с приводит к ошибке транслятора “Member is ambiguous A::x and A::x”. Эта ошибка означает, что транслятор не может определить, какому из двух компонент x класса необходимо присвоить новое значение. Неразрешимыми именами для транслятора будут также следующие с.C::x и c.A::x. Решением проблемы является использование квалифицированных имен компонент с использованием имен классов Bи D. Для транслятора однозначно различаются следующие имена компонент: с.B::x (компонента, унаследованная через класс В) и c.D::x (компонента, унаследованная через класс D). Именно из-за сложности управления одноименными унаследованными компонентами класса множественное наследование реализаций было запрещено в языках программирования, появившихся после С++ ( например, в C# и Java).

class A: public virtual D <…>;

class B: public virtual D <…>;

class C: public A, public B<…>;

Диаграмма классов в этом случае будет выглядеть как на рис. 5б. Компоненты косвенного базового класса присутствуют в классе С в единственном экземпляре, проблемы неоднозначности доступа к ним не возникает.

36. Шаблоны классов. Форматы описания шаблона класса, методов и объектов шаблонного класса.

Шаблоны классов.Возможность, обеспечивающая создание некого общего описания понятия стека (на основе родового класса создаются классы), являющегося специфическими версиями для конкретного типа данных наз-ся шаблонами классов.

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

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

Для того, чтобы использовать шаблонные классы, достаточно один раз описать шаблон класса. Шаблон класса Stack, например, может служить основой для создания многочисленных классовStackнеобходимых программе типов (таких, например, как *Stackдля данных типаfloat>>,

Идентификатор Т определяет тип данных-элементов, хранящихся в стеке, и может использоваться в заголовке класса Stack и в функциях-элементах.

Формат описания шаблонного класса следующий

Т является обозначением передаваемого в шаблон класса (типа данных). Служебное слово класс является признаком того, передаваемый в класс параметр- тип данных. Обозначение конкретного типа данных слева от передаваемого параметра означает, что данных параметр является константой. Например, шаблонный класс Massiv предназначен для организации классов массивов из элементов данных типа Т с количеством элементов N. Для организации класса массивов, состоящих из фиксированного количества элементов конкретного типа целесообразно использовать операцию typedef. Например, для описания класса массива целых чисел из 20 элементов следует использовать команду typedef Massive MassiveInt20; Далее можно определять конкретный массив MassiveInt20 M1; Возможен вариант определения массива непосредственно на основе шаблона Massive M2; Полное определение функций шаблона должно выглядеть следующим образом

>
37. Понятие программного обеспечения.

Вся совокупность программ, хранящихся на всех устройствах долговременной памятикомпьютера, составляет его программное обеспечение (ПО). если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть картинку если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Картинка про если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется

38.Жизненный цикл программы.

Жизненный цикл программного обеспечения (ПО) — период времени, который начинается с момента принятия решения о необходимости создания программного продукта и заканчивается в момент его полного изъятия из эксплуатации

Модели жизненного цикла ПО[править | править вики-текст]

Водопадная (каскадная, последовательная) модель[править | править вики-текст]

Основная статья: Модель водопада

Водопадная модель жизненного цикла (англ. waterfall model) была предложена в 1970 г. Уинстоном Ройсом. Она предусматривает последовательное выполнение всех этапов проекта в строго фиксированном порядке. Переход на следующий этап означает полное завершение работ на предыдущем этапе. Требования, определенные на стадии формирования требований, строго документируются в виде технического задания и фиксируются на все время разработки проекта. Каждая стадия завершается выпуском полного комплекта документации, достаточной для того, чтобы разработка могла быть продолжена другой командой разработчиков.

Этапы проекта в соответствии с каскадной моделью:

1. Формирование требований;

6. Эксплуатация и сопровождение.

· Полная и согласованная документация на каждом этапе;

· Легко определить сроки и затраты на проект.

Итерационная модель[править | править вики-текст]

Различные варианты итерационного подхода реализованы в большинстве современных методологий разработки (RUP, MSF, XP).

Спиральная модель[править | править вики-текст]

Спиральная модель (англ. spiral model) была разработана в середине 1980-х годов Барри Боэмом. Она основана на классическом цикле Деминга PDCA (plan-do-check-act). При использовании этой модели ПО создается в несколько итераций (витков спирали) методом прототипирования.

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

На каждой итерации оцениваются:

· риск превышения сроков и стоимости проекта;

· необходимость выполнения ещё одной итерации;

· степень полноты и точности понимания требований к системе;

· целесообразность прекращения проекта.

Отличительной особенностью спиральной модели является специальное внимание, уделяемое рискам, влияющим на организацию жизненного цикла, и контрольным точкам. Боэм формулирует 10 наиболее распространённых (по приоритетам) рисков:

1. Дефицит специалистов.

2. Нереалистичные сроки и бюджет.

3. Реализация несоответствующей функциональности.

4. Разработка неправильного пользовательского интерфейса.

5. Перфекционизм, ненужная оптимизация и оттачивание деталей.

6. Непрекращающийся поток изменений.

7. Нехватка информации о внешних компонентах, определяющих окружение системы или вовлеченных в интеграцию.

8. Недостатки в работах, выполняемых внешними (по отношению к проекту) ресурсами.

9. Недостаточная производительность получаемой системы.

10. Разрыв в квалификации специалистов разных областей.

39.Проблемы проектирования сложных программных средств.

Проблемы разработки ПО

Наиболее распространёнными проблемами, возникающими в процессе разработки ПО, считают:

· Недостаток прозрачности. В любой момент времени сложно сказать, в каком состоянии находится проект и каков процент его завершения.
Данная проблема возникает при недостаточном планировании структуры (или архитектуры) будущего программного продукта, что чаще всего является следствием отсутствия достаточного финансирования проекта: программа нужна, сколько времени займёт разработка, каковы этапы, можно ли какие-то этапы исключить или сэкономить — следствием этого процесса является то, что этап проектирования сокращается.

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

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

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

· Недостаточная надёжность. Самый сложный процесс — поиск и исправление ошибок в программах на ЭВМ. Поскольку число ошибок в программах заранее неизвестно, то заранее неизвестна и продолжительность отладки программ и отсутствие гарантий отсутствия ошибок в программах. Следует отметить, что привлечение доказательного подхода к проектированию ПО позволяет обнаружить ошибки в программе до её выполнения. В этом направлении много работали Кнут, Дейкстра и Вирт. Профессор Вирт при разработке Паскаля и Оберона за счет строгости их синтаксиса добился математической доказуемости завершаемости и правильности программ, написанной на этих языках.
Данная проблема возникает при неправильном выборе средств разработки. Например, при попытке создать программу, требующую средств высокого уровня, с помощью средств низкого уровня. Например, при попытке создать средства автоматизации с СУБД на ассемблере. В результате исходный код программы получается слишком сложным и плохо поддающимся структурированию.

· Отсутствие гарантий качества и надежности программ из-за отсутствия гарантий отсутствия ошибок в программах вплоть до формальной сдачи программ заказчикам.
Данная проблема не является проблемой, относящейся исключительно к разработке ПО. Гарантия качества — это проблема выбора поставщика товара (не продукта).

Источник

Виртуальные функции

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

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

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

Результат выполнения
если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть картинку если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Картинка про если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется

В терминологии ООП «объект посылает сообщение print и выбирает свою собственную версию соответствующего метода». Виртуальной может быть только нестатическая функция-член класса. Для порожденного класса функция автоматически становится виртуальной, поэтому ключевое слово virtual можно опустить.

Пример : выбор виртуальной функции

Результат выполнения
если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Смотреть картинку если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Картинка про если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется. Фото если класс имеет хотя бы один абстрактный метод чисто виртуальная функция то такой класс называется

Чистая виртуальная функция

Чистая виртуальная функция — это метод класса, тело которого не определено.

В базовом классе такая функция записывается следующим образом:

Для рассмотренного выше примера (класс Фигура) функцию вычисления площади целесообразно задать чистой виртуальной функцией, которую переопределяет каждый наследуемый класс.
Строка 9 при этом будет иметь вид:

Источник

Разработка интерфейсных классов на С++

Интерфейсные классы весьма широко используются в программах на C++. Но, к сожалению, при реализации решений на основе интерфейсных классов часто допускаются ошибки. В статье описано, как правильно проектировать интерфейсные классы, рассмотрено несколько вариантов. Подробно описано использование интеллектуальных указателей. Приведен пример реализации класса исключения и шаблона класса коллекции на основе интерфейсных классов.

Оглавление

Введение

Интерфейсным классом называется класс, не имеющий данных и состоящий в основном из чисто виртуальных функций. Такое решение позволяет полностью отделить реализацию от интерфейса — клиент использует интерфейсный класс, — в другом месте создается производный класс, в котором переопределяются чисто виртуальные функции и определяется функция-фабрика. Детали реализации полностью скрыты от клиента. Таким образом реализуется истинная инкапсуляция, невозможная при использовании обычного класса. Про интерфейсные классы можно почитать у Скотта Мейерса [Meyers2]. Интерфейсные классы также называют классами-протоколами.

Использование интерфейсных классов позволяет ослабить зависимости между разными частями проекта, что упрощает командную разработку, снижается время компиляции/сборки. Интерфейсные классы делают более простой реализацию гибких, динамических решений, когда модули подгружаются выборочно во время исполнения. Использование интерфейсных классов в качестве интерфейса (API) библиотек (SDK) упрощает решение проблем двоичной совместимости.

Интерфейсные классы используются достаточно широко, с их помощью реализуют интерфейс (API) библиотек (SDK), интерфейс подключаемых модулей (plugin’ов) и многое другое. Многие паттерны Банды Четырех [GoF] естественным образом реализуются с помощью интерфейсных классов. К интерфейсным классам можно отнести COM-интерфейсы. Но, к сожалению, при реализации решений на основе интерфейсныx классов часто допускаются ошибки. Попробуем навести ясность в этом вопросе.

1. Специальные функции-члены, создание и удаление объектов

В этом разделе кратко описывается ряд особенностей C++, которые надо знать, чтобы полностью понимать решения, предлагаемые для интерфейсных классов.

1.1. Специальные функции-члены

Если программист не определил функции-члены класса из следующего списка — конструктор по умолчанию, копирующий конструктор, оператор копирующего присваивания, деструктор, — то компилятор может сделать это за него. С++11 добавил к этому списку перемещающий конструктор и оператор перемещающего присваивания. Эти функции-члены называются специальные функции-члены. Они генерируются, только если они используются, и выполняются дополнительные условия, специфичные для каждой функции. Обратим внимание, на то, что это использование может оказаться достаточно скрытым (например, при реализации наследования). Если требуемая функция не может быть сгенерирована, выдается ошибка. (За исключением перемещающих операций, они заменяются на копирующие.) Генерируемые компилятором функции-члены являются открытыми и встраиваемыми.

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

Подробности о специальных функциях-членах можно найти в [Meyers3].

1.2. Создание и удаление объектов — основные подробности

Создание и удаление объектов с помощью операторов new/delete — это типичная операция «два в одном». При вызове new сначала выделяется память для объекта. Если выделение прошло успешно, то вызывается конструктор. Если конструктор выбрасывает исключение, то выделенная память освобождается. При вызове оператора delete все происходит в обратном порядке: сначала вызывается деструктор, потом освобождается память. Деструктор не должен выбрасывать исключений.

Если оператор new используется для создания массива объектов, то сначала выделяется память для всего массива. Если выделение прошло успешно, то вызывается конструктор по умолчанию для каждого элемента массива начиная с нулевого. Если какой-нибудь конструктор выбрасывает исключение, то для всех созданных элементов массива вызывается деструктор в порядке, обратном вызову конструктора, затем выделенная память освобождается. Для удаления массива надо вызвать оператор delete[] (называется оператор delete для массивов), при этом для всех элементов массива вызывается деструктор в порядке, обратном вызову конструктора, затем выделенная память освобождается.

Внимание! Необходимо вызывать правильную форму оператора delete в зависимости от того, удаляется одиночный объект или массив. Это правило надо соблюдать неукоснительно, иначе можно получить неопределенное поведение, то есть может случиться все, что угодно: утечки памяти, аварийное завершение и т.д. Подробнее см. [Meyers2].

Любую форму оператора delete безопасно применять к нулевому указателю.

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

1.3. Уровень доступа деструктора

1.4. Создание и удаление в одном модуле

Если оператор new создал объект, то вызов оператора delete для его удаления должен быть в том же модуле. Образно говоря, «положи туда, где взял». Это правило хорошо известно, см., например [Sutter/Alexandrescu]. При нарушении этого правила может произойти «нестыковка» функций выделения и освобождения памяти, что, как правило, приводит к аварийному завершению программы.

1.5. Полиморфное удаление

1.6. Удаление при неполном объявлении класса

warning C4150: deletion of pointer to incomplete type ‘X’; no destructor called

Ситуация эта не надумана, она легко может возникнуть при использовании классов типа интеллектуального указателя или классов-дескрипторов. Скотт Мейерс разбирается с этой проблемой в [Meyers3].

2. Чисто виртуальные функции и абстрактные классы

Концепция интерфейсных классов базируется на таких понятиях С++ как чисто виртуальные функции и абстрактные классы.

2.1. Чисто виртуальные функции

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

Чисто виртуальные функции могут быть определены. Герб Саттер предлагает несколько полезных применений для этой возможности [Shutter].

2.2. Абстрактные классы

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

2.3. Чисто виртуальный деструктор

В ряде случаев чисто виртуальным целесообразно сделать деструктор. Но такое решение имеет две особенности.

Пример использования чисто виртуального деструктора можно найти в разделе 4.4.

3. Интерфейсные классы

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

3.1. Реализации

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

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

3.2. Создание объекта

Недоступность класса реализации вызывает определенные проблемы при создании объектов. Клиент должен создать экземпляр класса реализации и получить указатель на интерфейсный класс, через который и будет осуществляться доступ к объекту. Так как класс реализации не доступен, то использовать конструктор нельзя, поэтому используется функция-фабрика, определяемая на стороне реализации. Эта функция обычно создает объект с помощью оператора new и возвращает указатель на созданный объект, приведенный к указателю на интерфейсный класс. Функция-фабрика может быть статическим членом интерфейсного класса, но это не обязательно, она, например, может быть членом специального класса-фабрики (который, в свою очередь, сам может быть интерфейсным) или свободной функцией. Функция-фабрика может возвращать не сырой указатель на интерфейсный класс, а интеллектуальный. Этот вариант рассмотрен в разделах 3.3.4 и 4.3.2.

3.3. Удаление объекта

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

Существуют четыре основных варианта:

3.3.1. Использование оператора delete

3.3.2. Использование специальной виртуальной функции

В этом варианте попытка удаления объекта с помощью оператора delete может компилироваться и даже выполняться, но это является ошибкой. Для ее предотвращения в интерфейсном классе достаточно иметь пустой или чисто виртуальный защищенный деструктор (см. раздел 1.3). Отметим, что использование оператора delete может оказаться достаточно сильно замаскированным, например, стандартные интеллектуальные указатели для удаления объекта по умолчанию используют оператор delete и соответствующий код глубоко «зарыт» в их реализации. Защищенный деструктор позволяет обнаружить все такие попытки на этапе компиляции.

3.3.3. Использование внешней функции

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

3.3.4. Автоматическое удаление с помощью интеллектуального указателя

3.4. Другие варианты управления временем жизни экземпляра класса реализации

3.5. Семантика копирования

Использование оператора копирующего присваивания не запрещено, но нельзя признать удачной идеей. Оператор копирующего присваивания всегда является парным, он должен идти в паре с копирующим конструктором. Оператор, генерируемый компилятором по умолчанию, бессмыслен, он ничего не делает. Теоретически можно объявить оператор присваивания чисто виртуальным с последующим переопределением, но виртуальное присваивание является не рекомендуемой практикой, подробности можно найти в [Meyers1]. К тому же присваивание выглядит весьма неестественно: доступ к объектам класса реализации обычно осуществляется через указатель на интерфейсный класс, поэтому присваивание будет выглядеть так:

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

Запретить присваивание можно двумя способами.

3.6. Конструктор интерфейсного класса

Часто конструктор интерфейсного класса не объявляется. В этом случае компилятор генерирует конструктор по умолчанию, необходимый для реализации наследования (см. раздел 1.1). Этот конструктор открытый, хотя достаточно, чтобы он был защищенным. Если в интерфейсном классе копирующий конструктор объявлен удаленным ( =delete ), то генерация компилятором конструктора по умолчанию подавляется, и необходимо явно объявить такой конструктор. Естественно его сделать защищенным с определением по умолчанию ( =default ). В принципе, объявление такого защищенного конструктора можно делать всегда. Пример находится в разделе 4.4.

3.7. Двунаправленное взаимодействие

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

3.8. Интеллектуальные указатели

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

3.9. Константные функции-члены

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

3.10. COM-интерфейсы

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

3.11. Интерфейсные классы и библиотеки

Достаточно часто интерфейсные классы используются в качестве интерфейса (API) для целых библиотек (SDK). В этом случае целесообразно следовать следующей схеме. Библиотека имеет доступную функцию-фабрику, которая возвращает указатель на интерфейсный класс-фабрику, с помощью которого и создаются экземпляры классов реализации других интерфейсных классов. В этом случае для библиотек, поддерживающих явную спецификацию экспорта (Windows DLL), требуется всего одна точка экспорта: вышеупомянутая функция-фабрика. Весь остальной интерфейс библиотеки становится доступным через таблицы виртуальных функций. Именно такая схема позволяет максимально просто реализовывать гибкие, динамические решения, когда модули подгружаются выборочно во время исполнения. Модуль загружается с помощью LoadLibrary() или ее аналогом на других платформах, далее получается адрес функции-фабрики, и после этого библиотека становится полностью доступной.

4. Пример интерфейсного класса и его реализации

4.1. Интерфейсный класс

Так как интерфейсный класс редко бывает один, то обычно целесообразно создать базовый класс.

Вот демонстрационный интерфейсный класс.

4.2. Класс реализации

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

4.3. Стандартные интеллектуальные указатели

4.3.1. Создание на стороне клиента

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

Для std::unique_ptr<> класс-удалитель является шаблонным параметром:

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

Вот шаблон функции-фабрики:

Вот шаблон преобразования из сырого указателя в интеллектуальный:

А этот ошибочный код благодаря защищенному деструктору не компилируется (конструктор должен принимать второй аргумент — объект-удалитель):

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

4.3.2. Создание на стороне реализации

Интеллектуальный указатель можно создавать на стороне реализации. В этом случае клиент получает его в качестве возвращаемого значения функциии-фабрики. Если использовать std::shared_ptr<> и в его конструктор передать указатель на класс реализации, который имеет открытый деструктор, то пользовательский удалитель не нужен (и не требуется специальная виртуальная функция для удаления объекта реализации). В этом случае конструктор std::shared_ptr<> (а это шаблон) создает объект-удалитель по умолчанию, который базируется на типе аргумента и при удалении применяет оператор delete к указателю на объект реализации. Для std::shared_ptr<> объект-удалитель входит в состав экземпляра интеллектуального указателя (точнее его управляющего блока) и тип объекта-удалителя не влияет на тип интеллектуального указателя. В этом варианте предыдущий пример можно переписать так.

Для функции-фабрики более оптимальным является вариант с использованием шаблона std::make_shared<>() :

4.4. Альтернативная реализация базового класса

Чисто виртуальный деструктор нужно определить, Delete() не чисто виртуальная функция, поэтому ее также нужно определить.

5. Исключения и коллекции, реализованные с помощью интерфейсных классов

5.1 Исключения

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

Реализовать Exception можно, например, следующим образом.

Класс реализации IException :

Определение конструктора Exception :

5.2 Коллекции

Шаблон интерфейсного класса-коллекции может выглядеть следующим образом:

С такой коллекцией уже можно достаточно комфортно работать, но при желании указатель на такой шаблонный класс можно обернуть в шаблон класса-контейнера, который предоставляет интерфейс в стиле контейнеров стандартной библиотеки.

6. Интерфейсные классы и классы-обертки

7. Итоги

Объект реализации интерфейсного класса создается функцией-фабрикой, которая возвращает указатель или интеллектуальный указатель на интерфейсный класс.

Для удаления объекта реализации интерфейсного класса существуют три варианта.

В первом варианте интерфейсный класс должен иметь открытый виртуальный деструктор.

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

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

Список литературы

[GoF]
Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования.: Пер. с англ. — СПб.: Питер, 2001.

[Josuttis]
Джосаттис, Николаи М. Стандартная библиотека C++: справочное руководство, 2-е изд.: Пер. с англ. — М.: ООО «И.Д. Вильямс», 2014.

[Dewhurst]
Дьюхэрст, Стефан К. Скользкие места C++. Как избежать проблем при проектировании и компиляции ваших программ.: Пер. с англ. — М.: ДМК Пресс, 2012.

[Meyers1]
Мейерс, Скотт. Наиболее эффективное использование C++. 35 новых рекомендаций по улучшению ваших программ и проектов.: Пер. с англ. — М.: ДМК Пресс, 2000.

[Meyers2]
Мейерс, Скотт. Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ.: Пер. с англ. — М.: ДМК Пресс, 2014.

[Meyers3]
Мейерс, Скотт. Эффективный и современный C++: 42 рекомендации по использованию C++11 и C++14.: Пер. с англ. — М.: ООО «И.Д. Вильямс», 2016.

[Sutter]
Саттер, Герб. Решение сложных задач на C++.: Пер. с англ. — М: ООО «И.Д. Вильямс», 2015.

[Sutter/Alexandrescu]
Саттер, Герб. Александреску, Андрей. Стандарты программирования на С++.: Пер. с англ. — М.: ООО «И.Д. Вильямс», 2015.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *