Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 194
Текст из файла (страница 194)
Только опытным программистам следует пытаться написать универсальную компоненту или библиотеку, и каждый разработчик библиотеки «обречен» на использование, документирование и поддержку своего создания в тече~не нескольких лет. См. также, пожалуйста, 8 23.5.1. 25.2. Конкретные типы Такие классы как пес1огЯ 16 3), Йз|Я 1? 2 2), Ра1е Я 103) п сатр1ех Я 11.3, з 22 5) являются конкре~пныжи в том смысле, что каждый из них представляет собой относительно простое понятие со всеми операциями, существенными для поддержки этого понятия. Также в каждолз из этих классов существует взаимнооднозначное соответствие между интерфейсом и реализацией, и ни один из этих классов не предназначен для создания производных классов.
Как правило, конкретные типы не вписываются в иерархию родственных классов. Каждый конкретный тип можно понять в отрыве от других классов, с минимальной связью с другими классами. Если конкретный тип хорошо реализован, использующие его программы по размеру и быстродействию сравнимы с программами, написанными вручную, или специализированными версиями для реализацшз той же концепции. Аналогично, если реализация значительно изменяется, обычно изменяется и интерфепс, чтобы отразить зто изменение. Вовсем этом конкретный тип напоминает встроенные типы.
Естественно, все встроенные типы тоже конкретные. Опредсляемые пользователем конкретные типы, такие как комплексные числа, матрицы, сообщения об оп|ибках, символические ссылки, часто являются фундаментальнымп типами для некоторых прикладных областей. Точная природа интерфейса класса определяет, какие изменения в реализации существенны в данном контексте; более абстрактные интерфейсы оставляют больше простора для изменения реализаций, но могут снизить быстродействие.
Кроме того, хорошая реализация не зависит от других классов болыпе, чем это абсолютно необходимо, так что класс можно использовать без расходов времени компиляции и выполнения, вызванных включением в программу других «схожих» классов. Подведем итог. Класс, представляюгций конкретный тип, предназначен для того, чтобы: [1] точно соответствовать данному частному понятию и стратегии реализации; [2] обеспечивать быстродействие и затраты памяти, сравнимые с «написанным вручную» кодом за счет применения встраивания и операций, полностью использующих особенности самого понятия и его реализации; [3] в мннимзльной степени зависеть от других классов; [4] быть понятным и пригодным к использованию независимо от других классов.
843 25.2. Конкретные типы Результат — тесная связь между пользовательским кодом н кодом реализации. Если реализация как-либо изменяется, пользовательский код придется перекомпилировать, поскольку пользовательский код почти всегда содержит обращения к встроенным функциям и локальным переменным конкретного типа. Название «конкретный тип» было выбрано по контрасту с распространенным термином «абстрактный тип». Взаимосвязь между конкретными и абстрактными типами обсуждается в 5 25.3. Конкретные типы не мо|ут непосредственно выражать общность.
Например, Дв! и пес!ог обеспечивают схожие наборы операций и могут взаимозаменяемо использоваться в каких-нибудь шаблонах функций. Однако между !т!<гя!> и пес!ог<!и!> или зяежду !!зГ<$йаре*> и !!з!<С!гс!в*> Я 13.6.3) никакой связи нет, хотя мы можем распознать нх сходство. Для бесхитростно спроектированных конкретных типов это приводит к тому, что код, использующий два разных типа схожим образом, будут выглядеть по-разному. Например, итерации по списку С з! при помощи птератора пек! (( резко отличаются от итерации по вектору (лес!оспри помощи индексации: ооЫ т у (/Ый з!', ( //«естественная»итвраиияпосписку !ог (Т' р=зЦ~гз! ~; р, р=зйпеяГ Я( //»~ой код ооИ уоиг (Уесзогс.
о( ( 0 <естеспввенная» итерация по вектору /ог (!я! 1=0; 1<явите ((, !.н-1 ( О ва~и код ) 0"- Разница в стиле |п ераций естественна в том смысле, что операция получения следующего элемента является основной для понятия списка (но это не так для вектора), а индексация является неотъемлемой чертой вектора (но не списка), !(оступность операций, которые «естественны» для избранной стратешш реализации, часто оказывается решающей для эффективности и облегчения написания программы. Очевидная сложность здесь заключается в том, что программы для фунламентально схожих операций, таких как два приведенных выше никла, могут выглядеть непохоже, а код, использующий разные конкретные типы для схожих операций, не взаымозаменяем.
В реальных примерах прзвходится хи|ого думать, чтобы найти сходство, и пцательно перепроектировать, чтобы добиться вьшоды от сходства, если оно найдено. Стандартные контейнеры и алгоритмы — вот пример тщательного продумывания, которое позволило задействовать сходство между конкретными тинами, не теряя их эффективности и ива|лестна Я 16.2).
Чтобы принимать конкретный тип в качестве аргумента, именно этот конкретный тнп должен быть описан в функции как тип аргумента. Не будет никаких отношений наследования, которые можно использовать, чтобы сделать объявление аргумента ме- Глава 25. Роли классов 844 нее специфическим. Поэтому попытка воспользоваться сходством между конкретными типами приводит к применению шаблонов и обобщенного программирования, как описано в 8 3,8. При использовании стандартной библиотеки, итерации выглядят так: сетр!ате<с!аее С> ооЫ оиге )С8 с) Хог )гурепате ссвегагогр=с ьеу!и ))гр)=сепг)))!+яр) ( // итерация согласно стандартной бибгиго~пеке 0- ) Здесь использовано фундаментальное сходство между контейнерами, что, в свою очередь, открывает возможность дальнейшей эксплуатации сходства в стандартных алгоритмах (глава 18), Чтобы хорошо использовать конкретный тип, пользователь должен понять его частные детали.
В библиотеке (как правило) не существует общих свойств для всех конкретных типов, на которые можно было бы положиться, чтобы избавить пользователя от необходимости вникать в отдельные классы. Это плата за быстродействие и эффективность. Иногда эта цена окупается, иногда нет.
Также может случиться, что легче понять и использова~ь пилив идуальный конкретный класс, чем более универсальный (абстрактный). Подобное час~о происходит с классами, представляющими хорошо известные типы данных, такие как массивы и списки. Однако отметим, что в идеале все жс лучше насколько возможно скргавать реализацию, пе внося серьезных ухудшений в производительность.
В данном контексте большой выигрьпп могут принести встроенные функции. Когда мы не скрываем члены данных, делая их открыл ыми или вволя функции, которые устанавливают и записывают их значения, это позволяет пользователсо напрямую ими манипулировать— такую идею очень редко можно назвать удачной (8 24А.2). Конкретные типы должны оставаться типами, а не просто мешками битов с нескольким функциями, введенными просто для удобства. 25.2.1.
Повторное использование конкретных типов Конкретные типы редко приносят пользу в качестве базовых классов для новых производных классов. Каждый конкретный тип нацелен на обеспечение ясного и эффективного представления одного понятия. Класс, хорошо с этим справляющийся, редко является хорошей кандидатурой для создания других, но родственных классов, через открытое наследование. Такие классы чаще приносят больше пользы в качестве членов илн закрытых базовых классов. Тут они могут использоваться эффективно, пе смешивая свои интерфейсы и реализации с интерфейсами и реализапиями новых классов, и не компрометируя их.
Рассмотрим создание производного класса от класса !!а!е: с!аее Му с!а!е: ри!г!!осла!е ( 0 ... Допустимо.лн использовать Му с(а!е вместо простого Ра!е? Это, конечно, зависит от того, что представляет собой Му с(а!е, но согласно моему опыту редко можно найти конкретный тип, который бы являлся хорошим базовым ю!ассом без модификации. 845 25.2. Конкретные типы Конкретные тинка кповторно используются» не модифицированным образом так же, как и встроенные типы, наподобие (п1 Я 10.3.4).
Например: с1азз Ра1е анд 1!те( рмиа1е: Ра1е а; Тлте 1; риЫсд Такая форма использования (повторного?) обычно проста, эффективна и продуктивна. Может быть, оыло ошибкой не проектировать Ра1е так, чтобы его было легко модифицировать через наследование? Иногда заявляют, что каждый класс должен быть открыт для модификации путем замещения функций-членов и доступа функций-членов производных классов. Эта точка зрения приводит примерно к такому варианту Ра1е: с1авв Ра1е2 ( 1зиЫ(с: // открыиый интерфейс, состоящий в основном из виртуальных функций ргв1ес1ед: // другиг детали реализации (возлюжно, включающие некоторое представление/ рггаа1е: //представление и другие детали реализации Чтобы сделать замещение функций, устанавливающих значения, легким и эффективнылл, представление объявлено защищенным.
Благодаря этому, класс Ра1е2 полностью готов к созданию произвольных классов, и в то же время его пользовательский интерфейс остается неизменным. Однако цена этого такова: 11] Менее эффективные базовгив операции. Обращение к виртуальным функциям в С++ немного медленнее, чем к обычным, виртуальные функции нельзя делать встроенньлми также часто, как невиртуальные, и класс с виртуальными функциями, как правило, приводит к перерасходу памяти по одному слову на объект. [2] Требуется использовить свободную паллятгк Цель ввеления Ра1е2 — позволить использовать объекты рззцых классов, производных от Ра1е2, взаимозаменяемым образом. Поскольку раамеры этих производных классов различны, очевидно следует располагать их в свободной памяти и обращаться к нпм через указатели и ссылки.