Программирование >>  Синтаксис инициирования исключений 

1 ... 28 29 30 [ 31 ] 32 33 34 ... 82


Если обратиться к классу Foo с запросом на получение в1ob, он вернет вам то, на что ссылается его собственная переменная. Если запросить Bar, а затем у Bar запросить Blob, вы получите другой объект - тот, на который ссылается Bar.

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

class Blob { ... };

class Foo : public Blob { ... };

class Bar : public Foo, public Blob { ... };

В этом фрагменте заложена неоднозначность. При преобразовании типа Bar* в Blob* непонятно, какой же Blob имеется в виду: непосредственно базовый для Bar или тот, от которого происходит Foo? Даже если вам удастся справиться с неоднозначностью, транзитивность все равно нарушается.

Как же избежать подобных проблем? Я мог бы пуститься в замысловатые объяснения, но все сводится к одному: следите за собой. Не наследуйте открыто от одного класса несколько раз в иерархическом дереве некоторого класса (с закрытым наследованием проблем обычно не возникает, поскольку нельзя выполнить преобразование к типу закрытого базового класса). Не внедряйте экземпляры одного класса в качестве переменных более чем одного компонента, а если уж вы это сделали - не предоставляйте интерфейсы более чем к одному из них.

Устойчивость граней

В эталонном C++ эта проблема возникает, например, при получении адреса переменной или базового класса и последующем удалении вмещающего объекта. Указатель на переменную или базовый класс превращается в мусор. Проблема обеспечения согласованности в такой неразберихе не решается за обедом в течение одного дня. Усиленная формулировка (указываемые объекты должны существовать до тех пор, пока для них существуют грани) откладывается до той части книги, которая посвящена управлению памятью. Слабая формулировка легко обеспечивается с помощью ведущих указателей. Если грань работает с объектом через ведущий указатель, то у ведущего указателя можно спросить: А указываешь ли ты сейчас на существующий объект? Если ответ будет отрицательным, оператор ! возвращает true, и функции начинают инициировать исключения. Правда, остается другая проблема - когда и как удалять ведущие указатели. Ее также лучше рассматривать при обсуждении различных стратегий управления памятью.

Грани и ведущие указатели

Концепции граней и кристаллов хорошо сочетаются с концепцией ведущих указателей. Существуют два основных подхода.

Ведущий указатель в середине

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

Превращение кристалла в ведущий указатель

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



Переходные типы

Основная идея переходного типа (malleable type) - класс, экземпляры которого как бы изменяют свой тип во время выполнения программы. Конечно, формально в C++ такого происходить не может - иначе вам пришлось бы всерьез и надолго подружиться с отладчиком. Тем не менее, чудеса современных указателей позволяют добиться почти того же эффекта.

Полиморфные указываемые объекты

В базовом варианте ведущего указателя присутствует переменная с типом Pointee*. Нo кто сказал, что объект, на который она ссылается, должен быть настоящим Pointee, а не каким-нибудь классом, производным от Pointee?

В файле Foo.h class Foo { protected:

Foo();

public:

Члены Foo

class Pfoo { Ведущий указатель private:

Foo* foo; public:

PFoo();

Foo* operator->() const { return foo; }

Остальные члены, характерные для ведущих указателей

В файле Foo.cpp

class DerivedFromFoo : public Foo {

private:

Закрытые члены производного класса public:

DerivedFromFoo(); Функция открытая, но спрятанная в файле .cpp Переопределения функций класса Foo

PFoo::PFoo() : foo(new DrivedFromFoo)

Ловкость рук и никакого мошенничества! Ведущий указатель подсунул вместо Foo нечто совершенно иное, а клиенты ничего не замечают. Ага! Теперь вы понимаете, почему конструктор Foo объявлялся защищенным, а не закрытым! Класс PFoo уже можно не объявлять другом; доступ к конструкторам Foo нужен только конструктору DerivedFromFoo.

В части 3 мы поговорим о том, какое наследование нужно, чтобы эта схема работала (а именно, чтобы все классы, производные от Foo, имели тот же открытый интерфейс, что и сам Foo). А пока продолжим изучение указателей и всего, что с ними связано.



Выбор типа указываемого объекта во время конструирования

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

В файле Foo.cpp

class DeirvedFromFoo : public Foo { ... }; class AlsoDerivedFromFoo : public Foo { ... };

PFoo::PFoo(boo1 x) : foo(x ? new DerivedFromFoo : new AlsoDerivedFromFoo) {}

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

Изменение указываемого объекта во время выполнения программы

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

class Foo; class PFoo { private:

Foo* foo; public:

PFoo();

void DoSomething(boo1 x); Другие функции класса

void PFoo::DoSomething(boo1 x)

if (x) {

delete foo;

foo = new DerivedFromFoo;

Foo->DoSomething();

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

Посредники

Интерфейсные указатели также помогают скрыть тот факт, что указываемый объект находится где-то в киберпространстве, а не сидит в памяти по соседству. В распределенных объектных системах такое происходит сплошь и рядом. Первая цель проектировщика - держать клиентские объекты в блаженном неведении; они не знают и знать не хотят, где находится указываемый объект - на расстоянии плевка или где-то на краю земли. Объект, который заменяет другой, удаленный объект, называется посредником (proxy). На эту тему тоже существует немало вариаций, но самая простая из них - локальное использование интерфейсного объекта или грани. Затем локальный посредник может воспользоваться дистанционными вызовами или другим механизмом отправки сообщений, подходящим для взаимодействия с оригиналом.

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



1 ... 28 29 30 [ 31 ] 32 33 34 ... 82

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