Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 105
Текст из файла (страница 105)
Однако, это не всегда так: с!аяеМапауег риЫЫЕтр1оуее ( !п1 !еое1; ооЫ7"!) ( Етр!оуее* р = лего Малауег; г(е(еге р, ) /!' проблема: истинниб тип потерян В этом случае компилятор не знает реального размера. Так же как и при удалении массива, требуется помощь от пользователя. Это производится добавлением вирту- альцого деструктора к базовому классу, Етр1оуее: с1аех Етр!оуее ( риЫ!а ооЫ' орега1ог пет (е!ге 1), ооЫ орего1ог с(е1е1е (ио!г(*, есге й; о!г(иа! -Етр!оуее (); 0- !!одойдет даже «пустой деструктор: Егпр!оуее..-Етр1оуее () () В првнпипе, освобождение памяти осуществляется тогда внутри деструктора (который знает размер).
Более того, наличие деструктора в Етр1оуее дает гарантию, что каждый производный класс будет иметь деструктор (и выдавать правильную информацшо о размере), даже если в нем отсутствуют явно определенные пользователем деструкторы. Например: иои1 Я ( Етр1оуее*р = пет Малауег; с!е1е1е р; О теперь праеияьно (Етр1оуее — полияюрфен) Етр1 оуеесорега1ог лет (е! ген (Мапауег) ) Выделение памяти осуществляется при помощи (сгенерированного компилятором) вызова: 479 15.6.
Свободная память а освобождение памяти — при помощи [тоже сгенерированного компилятором) вызова: Етр!оуеесорега1огае!е1е В>, к1гео/(Мапауег)) Другими словами, если вы хотите иметь пару для выделения/освобождения памяти, которая корректно работает с производными классами, вы должны либо предоставить виртуальный деструктор в базовом классе, либо воздержаться от использования аргумента типа кгке 1 при освоболсдении памяти. Естественно, можно было спроектировать этот аспект языка такнм образом, чтобы вы не имели перечисленных проблем. Однако при этом вы бы не имели и возможности оптимизации, присущей менее безопасным системам.
15.6.1. Выделение памяти под массив Функции ореги1ог лев () и орега1ог с!е[е1е() дают возможность пользователю заместить выделение и освобождение памяти для индивидуальных объектов: орега1ог пещи () и орега1ог с(е1е1еЦ () играют ту же роль для массивов. Нащгнмер: с!акк Етр1оуее ( риЫгс эоЫ" орега1ог пегсЦ (к1зе 1), эоЫ орега1ог ае!е1еЦ (соЫ'); //- ); эоЫ/(1п1 к) ( етр!оуее" р = пеш Етр!оуее[к], //- Ие!е1еЦ р, Память будет выделяться при помощи вызова, Етр!оуеесорегагог пеги[] (кьксаео/(Етр!оуее»»ае!1а) где с(е!1а — некоторая необходимая дополнительная память, объем которой зависит от реализации. Освобождение памяти будет осуществляться прп помощи вызова: Етр1оуее; орега1огйе!е1е[] (р), //асвооожбаст к'кьтео/[Етр!оуее) де11а байт Количество элементов [к) система «помнит».
В отличие от одноаргументной двухаргументная форма функции с1е1е1еЦ () позволяет вызывать ее со вторым аргументом типа к*к1аеоу' (Етр/оуее)»г/е/1а. 15.6.2. «Виртуальные конструкторы» Пос.ле знакомства с виртуальными деструкторами возникает очсвидный вопрос; «Может ли конструктор быть виртуальным? . Краткий ответ — нет; более подробный тоже нет, но вы легко может получить желаемый эффект. Для того чтобы создать обьект, конструктор должен знать его точный тип. Следовательно, конструктор пе может быть виртуальным.
Более того, конструктор является не совсем обычной функцией. В частности, он взаимодействует с процедурами управления памятью способом, недоступным обычным функциям-членам. Как следствие, вы не мо;кете получить указатель на конструктор. 480 Глава 15. Иерархии классов с1аее Ехрг( риЫяс Ехрг (); Ехрг (сопк1 Ехргв ), // констру ктор по улол канаю // копируюи(ии конструктор о(ггиа!Ехрг'пев ехрг()(геяигп певЕхрг();) оягсиа1 Ехрт с1опе () ( ге1игп пело Ехрг ('1Ые); ) //" Так как функции вроде пев ехрг () и с1опе () являются виртуальными, яя они (косвенно) создают объекты, их часто называют евиртуачьнымяя конструкторами» вЂ”.
пример странности естественного языка. На самом деле, каждая из них просто использует конструктор для создания подходящего об ьекта. Для возвращения объекта собственного типа производный класс может заместить пев ехрг () и/или с1опе (); с1аек Сопя(. риЫсс Ехрг ( риЫлс: Сопб 9; Сопи (сопесСолиК); Сопя/'пев ехрг() (гегигп лев Сопя(();) Сопя(" с(о не () ( ге(ига пев Сопя( ('1Ые), ) //- Это означает, что если имеется объект класса Ехрг, пользователь может создать но- вый объект еточно такого же типам Например: оо(а иеег (Ехрг' р) ( Ехрг' р2 = р->лев ехрг (); //- Указатель, присвоенный р2, имеет корректный, но неизвестный тип.
Значения, возвращаемые Сопя(спев ехрг() и Сопя(сс1опе (), имеют тип Сопя(', а не Ехрг'. Это позволяет создавать копии Сопя)оез потери информации о типе. Например: сои( иеег2 (Сопя(" рс, Ехргл ре) ( Сопя(' р2 = рс — >с1опе (); Сопя(*рЗ =ре — >с1опе (); //ошибка //-. Оба ограничения можно обойти, определив функцию, которая вызывает конструктор и возвращает созданный объект. Это хорошо, так как часто возникает необходимасть в создании объекта, точный тип которого не известен. Примером класса, специально спроектированного для этой цели, может служить Раа1 Ьох тайег Я 12А.4). Сейячас я представлю другую вариацию этой идеи, где объекты класса могут создавать клоны (копяяи) самих себя или новые объекты своего типа.
Рассмотрим пример: 481 15.8. Упражнения Тцп замещающей функции должен быть такой же, как тип виртуальной функции, которую оца замещает, за исключением того, что допускаются некоторыс ослабления по отношени1о к типу возвращаемого значения. Например, если исходный тип возвращаемого значения был В*, то тип возвращаемого значения замещакпцей функции может быть Р* при условии, что В является открытым базовым классом для Р, Аналопщно, вместо ВЬ тип возвращаемого значения может быть ослаблен до ~И. Обратите внимание, что подобные правила «ослабления типа» для аргументов привелии бы к нарушениям типов (см. 8 15.8[12]). 15.7. Советы [1) Пользуйтссь обычным множественным наследованием, чтобы выразить объединение свойств; з 15.2, ~ 15.2.5.
(2) Пользуйтесь множественным наследованием дтя отделения деталей реализации от интерфейса; 6 15.2.5. [3) Используйте виртуальные базовые классы для выражения некой общности, присущей некоторым, но не всем классам иерархии; ф 15.2.5. [4) Избегайте явного преобразования типа (приведения); ч 15.4,5. [5) Пользуйтесь динамическим приведением г(упащ!с сазб когда навигация по иерархии нецзбежна; з 15.4.1.
[6) Отдавайте предпочтение Нупат!с саз1 по отношению к илентификатору типа 1дреЫ; ~ 15А.4. [7) Отдавайте предпо пение рг!па(е по отношению к рго(ес(есь, 6 15.3.1.1. [8) Не объявляйте члены данных защищенными; ~ 15.3.1.1. [9) Если класс определяет орега1огг(е1е(е (), в нем должен присутствовать виртуальный деструктор; з 15.6.
(10) Не вызывайте виртуальные функции в процессе создания или уничтожения объекта; () 15А.3. [11) Умеренно пользуйтесь явными квалифнкаторами д.ля разрешения перегрузки имен членов и старайтесь применять их в замещающих функциях; ч 15.2.1. 15.8. Упражнения 1. (*1) Напишите шаблон р(г сазй который работает аналогично г(длат(с саз(затем исключением, что вместо возвращения О, он возбуждает цсключение баг( сазб 2. (*2) Напишите программу, которая иллюстрирует влияние последовательности вызовов конструкторов на состояние объекта (с точки зрения КТТ1). Аналогичным образом пронллюстриру йте уничтожение объекта.
3. (*3 5) Реализуйте версию настольной игры Реверси (ксчегы70ге)!о). Каждый игрок мо- . жет быть либо человеком, либо компьютером. Сфокусируйтесь на том, чтобы программа работала корректно, и поднимите качество игры компьютера до достойного уровня. 4. (*3) Сделайгге более качественный интерфейс для игры из з ! 5.8[3). 5. (*3) Определите класс графических объектов с разумным набором операций, который мог бы служить в качестве базового класса библиотеки графических объектов; посмотр~п е какие операции реализованы в реальной графической библиотеке. Определите класс объектов для работы с базами данных с подходящим набором о пера- 482 Глава 15.
Иерархии классов ций, который мог бы использоваться в качестве базового класса для объектов, хранящихся как последовательность полей в базе ланных; посмотрите какие операции реализованы в реальнои библиотеке баз данных, Определите графический объект, связанный с базой данных, с использованием и без использования множественного наследования и обсудите относительные преимущества каждого подхода. 6, (*2) Напишите версию операции с1опе () из ч 15.б.2, которая может поместить скопированный объект в заданную область памяти Агепа (см, б 10А.11), переданную ей в качестве аргумента. Реализуйте класс для работы с фиксированными областямп памяти («аренамнь) в виде класса, производного от Агепа.
7. («2) Не заглядывая в эту книгу, напишите как можно больше ключевых слов С++, 8. ('2) Напишите корректную программу на С++, содержащую последовательность по крайней мере из десяти идущих полряд различных ключевых слон, не разделенных идентификаторами, операторами, символами пунктуации н т. д. 9. (*2.5) Изобразите правдоподобное распределение памяти для объектов классаВас(1о пз 8 15 23 1. Объясните, как мог бы быть реализован вызов виртуальной функции, 10. (*2) Изобразите правдоподобное распределение памяти для объектов класса Вайо из з 15.2А.