А. Александреску - Современное проектирование на C++ (1119444), страница 6
Текст из файла (страница 6)
Любой опытный разработчик классов знает, что этот наивный подход не работает. Анализ причин, по которым множестненное наследование не позволяет воплошать гибкие проектные решения, помогает найти правильный выход. Проблемы заключаются в следуюшем.
1. Механика. Не сугцествует стандартного кода, позволяюшего объединить наследуемые компоненты в одно целое стандартным образом. Елинственным инструментом, даюшим возможность объединить классы вазенпагтпгг, мц1- с(тпгеаг)ед и яебсоиптег(, является языковый механизм, называемый ммолгественлым наследованием (гпи!!!р!е гпйегйапсе). Для объединения базовых классов применяется простая суперпозиция и устанавливаются правила доступа к их членам. Это совершенно недопустимо и работает лишь в простейших случаях. Чаше всего для достижения желаемого эффекта программист вынужден тшательно настраивать поведение наследуемых классов. 2. Ингрормация о тинах.
Базовые классы не имеют достаточно информации о типах, чтобы выполнить свою работу. Представим, например, что мы пытаемся реализовать глубокое копирование интеллектуального показателя с помошью наследования от базового класса оеерсору. Какой интерфейс должен иметь класс ОеерСору? Очевидно, что он вынужден создавать обьекты, имеюшие тип, ь о котором ему ничего не известно. 3. Изменение саслюяиия. Различные аспекты поведения, реализованные в базовых классах, должны влиять на одно и то же состояние.
Это означает, что они долж- ' Этот пример создан иа основе старого аргумента, который Бьярп Страуструп (В)агп 5ггоизггир) привел в пользу множественного наследования в первом издании книги "Пге С++ РюНгатт!пд (лпяпане" (см. Страуструп Б. "Язык программирования С++". — Мз Мир, !990.— Прим. ред.), В то время множественное наследование а языке С++ еше ие было реализовано. 28 Часть !. Методы ны применять виртуальное наследование от базового класса, имеюшего заданное состояние. Это усложняет разработку и делает ее негибкой, поскольку предполагается, что классы пользователя наследуют свойства от библиотечных классов, а не наоборот.
Комбинаторное по своей природе, множественное наследование само по себе нельзя применять для поддержки многовариантных проектных решений. 1.4. Преимущества шаблонов Для имитации комбинаторного поведения хорошо подходят шаблоны, поскольку они позволяют генерировать код во время компиляции, основываясь на типах, указанных пользователем. По способу настройки шаблонные классы принципиально отличаются от обычных. Если нужно реализовать какой-то специальный вариант, можно специализировать (зрес!а!!ге) любые функции-члены шаблонного класса, конкретизировав его.
Например, если задан шаблон Звагтртг<т>, можно специализировать любую функцию-член класса ввагготг<и(орет>. Это позволяет хорошо детализировать повеление настраиваемого шаблона. более того, лля шаблонных классов с несколькими параметрами можно использовать частичную шаблонную специализацию (как мы увидим в главе 2). Это лает возможность специализировать только некоторые из аргументов шаблонного класса. Например, с помошью специализации тевр1ате <с1азз т, с1азз о> с1авв вваггртг ( ... ); можно специализировать шаблон вваггогг<т, ц> для класса и(дует, а также для любого другого типа, используя слелуюшую синтаксическую конструкцию: тевр1ате <с1Ьз ц> с1аав ввагтотг<в(одет, ц> ( ...
); Статическая и комбинаторная природа шаблонов делает их очень привлекательными для создания фрагментов проектных решений. Однако на пути реализации такого подхода сушествует несколько неочевидных препятствий. !. Невозможно специализировать структуру.
Используя лишь шаблоны, невозможно специализировать структуру класса (т,е. его данные-члены). Специализировать можно только функции. 2. Специализация функций-членоо не мапитабируется. Можно специализировать любую функцию-член шаблонного класса с одним шаблонным параметром, но невозможно специализировать отдельные функции-члены для шаблонов с несколькими шаблонными параметрами. Проиллюстрируем зто утверждение примером. севр1ате <с1азз т> с1ава и1одет ( чо)й оипО ( ... обобщенная реализация )! // Ок: специализация Функции-члена класса и!ойег тевр1аге <> !я(одет<спас>;;рцпО ...
специализированная реализация ... ) тевр1ате <с1ааа т, с1азз ц> с1азз цадйет ( чохе! рцпО ( ... обобщенная реализация ... ) 29 Глава 1. Разработка классов на основе стратегий // ошибка! частичная специализация функции-члена // класса падйес иевозиожиа! севр1асе <с1азз ц> чо!д падйеС<сЬаг, ц>::гцпО ( ( ... специализированная реализация 3. Автор библиотеки не может задать несколько значений ла умолчанию. В лучшем случае программист, реализующий шаблонный класс, может задать для каждой функции-члена одну реализацию по умолчанию. Шаблонная функция-член не может иметь несколько аргументов, заданных по умолчанию, Сравним список нелостатков множественного наследования со списком недостатков шаблонов.
Интересно, что множественное наследование и шаблоны пополняют друг друга. Множественное наследование устроено довольно примитивно; шаблоны, наоборот, обладают богатыми возможностями специализации. Множественное наследование теряет информацию о типах; шаблоны ими изобилуют. Специализация шаблонов, в отличие от множественного наследования, не масштабируется. В шаблонах для функции-члена можно задать только один аргумент по умолчанию и создать неограниченное количество базовых классов.
Все это позволяет предположить, что с помощью комбинирования шаблонов и множественного наследования можно сконструировать очень гибкий меканизм„пригодный для создания библиотек, содержащих готовые элементы программного обеспечения. 1.5. Стратегии и классы стратегий Стратегии и классы стратегий позволяют реализовать безопасные, эффективные и легко настраиваемые элементы проектных решений. Стратегия (ро)!су) определяет интерфейс обычного или шаблонного класса. Этот интерфейс состоит из определения внутренних типов, функций-членов и переменнь<х-членов. Понятие стратегии имеет много общего с характеристиками (<гайз) (/<)ехапдгеасц, 2000а), но отличается тем, что в них меньше внимания улеляется типам и больше— поведению.
Кроме того, понятие стратегии, предложенное нами, напоминает стратегии нровктираванил (Оапппа е< а!., !995), но в отличие от них классы стратегий связываются на этапе компиляции. Например, определим стратегию для создания объектов. Стратегия Сгеасог описывает шаблонный класс типа т. Этот шаблонный класс должен предоставить функцию- шеи с именем Сгеасе, не имеющую аргументов и возвращающую указатель на объект класса т, Это означает, что каждый вызов функции сгеасе должен возвращать указатель на новый объект класса т. Точный режим создания объекта определяется во время реализации стратегии.
Определим несколько классов стратегий, реализующих стратеппо Сгеасог. Для этого существует трн способа. Во-первых, можно использовать оператор пеш. Во-вторых, вместо оператора пеш можно вызвать функцию ша11ос (Меус<к, !998Ь). В-третьих, новые объекты можно создавать, кконируя их прототипы. Рассмотрим эти методы. сешр1асе <с1аав т> зсгисс орнеч<сгеасог ( зсас!с т* сгеасеО гесигп пеш т; Часть!. Методы 30 ) ); сеер1асе <с1азз т> зсгцсс ма11оссгеасог ( зсасзс т* сгеасеО ( чозд» ЬИ = зсб: наа11ос(ззаеот(т)); зФ (!ЬцО гесцгп 0; гесцгп пен(ЬцФ) т; ) ); севр1асе <с1ааа т> зСгцСС РготоСуреСгеатог ( Ргососуресгеасог(т» роЬ) = О) :рргососуре (роЬ)) () т* сгеасеО ( гесцгп рргососуре ? рвгососуре ->с1опеО : 0; ) т* оеспгососуреО ( гесцгп рпгососуре; ) чоза бесвгососуре(т» роь)) (рвгососуре = роь);) ргз часе: т* рргоСоСуре ; ); Данная стратегия может иметь сколько угодно реализаций.
Реализации стратегий называются классами стратегийз. Классы стратегий по отдельности не используются. Они либо наследуются другими классами, либо содержатся в них. Следует отметить, что, в отличие от классических интерфейсов (коллекций чисто виртуальных функций), интерфейсы стратегий не имеют четкого определения. Стратегии ориентируются на синтаксис, а не сигнатуру. Иными словами, стратегия Сгеасог опрелеляет, какая синтаксическая структура класса считается правильной, и не уточняет, какие именно функции класс должен реализовывать. Например, стратегия Сгеатог не указывает, должна ли функция Сгеате быть статической или виртуальной.
Она лишь требует, чтобы шаблонный класс определял функцию-член сгеасе. Кроме того, стратегия сгеасог выражает пожелание (но не требует), чтобы функция сгеасе возврашала указатель на новый объект. Следовательно, вполне допускается, что в отдельных случаях функция-член Сгеасе может возврашать нуль или генерировать исключительную ситуацию. Для одной стратегии можно реализовать несколько классов. Все они должны соответствовать интерфейсу, определенному стратегией. Затем, как мы вскоре убедимся, пользователь сам выберет, какой класс стратегии следует использовать в более крупных структурах. Три класса стратегий, определенных выше, имеют разные реализации и даже слегка отличаюшиеся интерфейсы (например, класс РгососуреСгеасог имеет две дополнительные функции — оеспгососуре и бесргососуре), Однако все они определяют з Это название немного неточное, поскольку, как мы вскоре убелимся, реализациями стратегии могут быть и шабланные классы.