Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 86 87 88 [ 89 ] 90 91 92 ... 144


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

Сегодня мне кажется, что указанные проблемы имеют фундаментальный характер. Для решения первой потребовалось бы изменять таблицу виртуальных функций объекта, которому делегируется управление, если он связан с делегиру-юшим объектом. Это плохо согласуется с языком в целом и с большим трудом поддается разумному определению. Кроме того, обнаружились примеры, когда мы хотели, чтобы два разных объекта делегировали управление одному и тому же разделяемому объекту. Нашлись и такие задачи, в которых нужно было делегировать управление через В* объекту производного класса D.

Поскольку делегирование не поддержано в С++ напрямую, нужно искать обходные пути, если оно все же пеобходи.мо. Часто решить проблему, требуюшую делегирования, можно с помошью умного указателя (см. раздел 11.5.1). Вместо этого делегирующий класс может предоставить полный интерфейс, а затем запросы вручную переадресовываются какому-то другому объекту (см. раздел 11.5.2).

12.8. Переименование

в конце 1989 - начале 1990 гг. была обнаружена проблема, проистекающая из конфликта имен в иерархиях с множественным наследованием [ARM]:

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

class Lottery { лотерея

...

virtual int drawO; тянуть жребий

class GraphicalObject { графический объект .. .

virtual void draw(); рисовать

class LotterySimulation моделирование лотереи : public Lottery, public GraphicalObject { .. .

В классе LotterySimulation ном хотелось бы заместить кок Lottery: : draw (), так и Gr aphicalOb j ect: : dr aw {}, но двумя разными функциями, поскольку экземпляры draw () в базовых классах имеют розную семантику. Желательно, чтобы в клоссе LotterySimulat ion были различные, не допускающие двусмысленного толкования имена для унаследованных функций Lottery::draw() и GraphicalObject::draw{).

Семантика концепции очень проста, о реализация тривиальна; трудно лишь найти подходящий синтаксис. Предлагался такой ворионт:

class LotterySimulation

: public Lottery , public GraphicalObject {



...

virtual int l draw() = Lottery::draw;

virtual int go draw() = GraphicalObject::draw;

Это естественное росширение синтаксиса виртуальных функций .

После продолжительной полемики в рабочей группе по расширениям и подготовки необходимых документов Мартином ОРиорданом и мной, предложение было вынесено на заседание комитета по стандартизации в Сиэтле в июле 1990 г. Подавляющее большинство участников обсуждения высказалось за то, чтобы сделать это первым сторонним расширением C-i-i-. Но в этот момент Бет Крокетт (Beth Crockett) из компании Apple приостановила работу комитета, воспользовавшись правилом двух недель : любой представитель комитета может перенести голосование по предложению на следующее заседание, если тема обсуждения не была передана ему, по крайней мере, за две недели до начала текущего заседания. Это правило защищает от поспешного принятия решения по вопросу, в которо.м человек разобрался не до конца, и гарантирует достаточное время для консультаций с коллегами.

Вы, наверное, догадались, что этим вето Бет не снискала популярности. Однако ее осторожность оказалась не напрасной и уберегла нас от серьезной ошибки. Спасибо! Когда мы вернулись к рассмотрению вопроса. Дуг Макилрой заметил, что проблема и без того имела решение в C-i-i- [ARM]:

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

class LLottery : public Lottery { virtual int l draw() = 0;

int drawO { return l draw(); } замещает

class GGraphicalObject : public GraphicalObject { virtual void go draw() = 0; void drawO { go draw(); } замещает

class LotterySimulation

: public LLottery , public GGraphicalObject { . . .

int l draw(); замещает int go draw(); замещает

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

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



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

Еще одно, более общее возражение против переименования: я не люблю отслеживать цепочки псевдонимов при сопровождении кода. Если я вижу в тексте имя f, которое в заголовке определено как д, в документации названо h, а в коде вызывается как к, то проблема налицо. Разумеется, приведенный пример - крайность, но в таком стиле могут работать приверженцы макросов.

Синонимы бывают полезны, а иногда и необходимы. Однако их употребление следует свести к минимуму, если мы хотим сохранить ясность и общность кода в различных контекстах. Благодаря новым средствам, прямо поддерживающим переименование, началось бы простое потворство использованию (в том числе, и необоснованному) синонимов. Это соображение стало и доводом против общего механизма переименования при обсуждении пространств имен (см. главу 17).

12.9. Инициализаторы членов и базовых классов

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

class X : public А, public В { int XX;

X(int а, int b)

: A(a), инициализации базового класса А B(b), инициализации базового класса В XX(1) инициализация члена хх

Этот синтаксис инициализации параллелен синтаксису инициализации объектов класса:

А Х(1); В У(2);

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

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



1 ... 86 87 88 [ 89 ] 90 91 92 ... 144

© 2006 - 2025 pmbk.ru. Генерация страницы: 0.002
При копировании материалов приветствуются ссылки.
Яндекс.Метрика