Программирование >>  Оптимизация возвращаемого значения 

1 ... 54 55 56 [ 57 ] 58 59 60 ... 96


SmartPtr<Cassette> funMusic(new Cassette( Alapalooza )); SmartPtr<CD> nightmareMusic(new CD( Disco Hits of the 70s )); displayAndPlay(funMusic, 10); Ошибка!

displayAndPlay(nightmareMusic, 0); Ошибка!

Если интеллектуальные указатели так умны, почему этот код не компилируется?

Он не компилируется из-за того, что не существует преобразования из SmartPtr<CD> или SmartPtr<Cassette> в SmartPtr<MusicProduct>. Сточки зрения компилятора, это три отдельных класса, которые не связаны друг с другом. С какой стати компиляторам думать иначе? Непохоже, что Smart Ptr<CD> или SmartPtr<Cassette> наследуют от SmartPtr<MusicProduct>. Если данные классы не связаны между собой отношениями наследования, едва ли можно ожидать, что компиляторы будут сами по себе преобразовывать объекты одного типа в объекты другого.

К счастью, существует простой (если дело не касается практики) способ обойти это ограничение: определить в каждом классе интеллектуального указателя оператор явного преобразования типов (см. правило 5) для каждого из других классов интеллектуальных указателей, в которые он должен неявно конвертироваться. Например, в иерархии музыкальных носителей к классам интеллектуальных указателей Cassette и CD добавляются функции operator Smart Ptr<Mu-sicProduct>:

class SmartPtr<Cassette> { public:

operator SmartPtr<MusicProduct>()

{ return SmartPtr<MusicProduct>(pointee); }

private:

Cassette *pointee;

class SmartPtr<CD> { public:

operator SmartPtr<MusicProduct>()

{ return SmartPtr<MusicProduct>(pointee); }

private:

CD *pointee;

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



template<class Т> class SmartPtr { public:

SmartPtr(Т* realPtr = 0)

Шаблон класса

для интеллектуальных

указателей на объекты Т.

Т* operator->{) const; Т& operator*{) const;

template<class newType> Функция шаблона

operator SmartPtr<newType> {) для операторов неявного

{ преобразования типов,

return SmartPtr<newType>(pointee);

Это почти волшебство, и сейчас вы узнаете, как оно происходит. (Я вскоре приведу конкретный пример, поэтому не отчаивайтесь, если остаток параграфа покажется вам бессмысленным набором слов. После того как вы увидите код, вам все станет ясно, обещаю.) Предположим, компилятор располагает интеллектуальным указателем на объект Т, и он должен превратить этот объект в интеллектуальный указатель на базовый класс объекта Т. Компилятор проверяет, объявлен ли искомый оператор преобразования в классе SmartPtr<T>, но такой оператор не объявлен. (И этого не может быть: в вышеприведенном шаблоне не объявлены операторы преобразования.) Затем компилятор проверяет, существует ли какая-нибудь функция-член, позволяющая выполнить требуемое преобразование. Он находит шаблон такой функции (с формальным параметром newType), потом создает экземпляр шаблона, в котором параметр newType привязан к базовому классу Т, являющемуся целью преобразования. Единственный вопрос заключается в том, будет ли код полученной функции-члена компилироваться. Для этого должна быть допустима передача указателя (обычного) pointee конструктору интеллектуального указателя на базовый класс т. Указатель pointee имеет тип Т, поэтому, конечно, разрешается его превращение в указатель на соответствующие

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

Если бы можно было как-то заставить компилятор автоматически написать функции неявного преобразования типов, то это сэкономило бы массу времени. И такое возможно благодаря недавнему расширению языка, которое позволяет объявлять (невиртуальные) шаблоны функций-членов (member function templates или часто member templates). Эти шаблоны вы можете использовать для создания таких функций преобразования типов для интеллектуальных указателей:



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

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

void displayAndPlay{const SmartPtr<MusicProduct>& pmp,

int howMany);

SmartPtr<Cassette> funMusic(new Cassette{ Alapalooza )); SmartPtr<CD> nightmareMusic (new CD ( Disco Hits of the 70s ) ) ; displayAndPlay(funMusic, 10); Здесь была ошибка.

displayAndPlay(nightmareMusic, 0); Здесь была ошибка.

Если же исправить класс интеллектуальных указателей, содержащих шаблон функции-члена для операторов неявного преобразования типов, этот код будет успешно компилироваться. Чтобы понять почему, рассмотрим вызов;

displayAndPlay(funMusic, 10);

Объект funMusic имеет тип SmartPtr<Cassette>. Функция же displayAndPlay ожидает объект SmartPtr<MusicProduct>. Компиляторы обнаруживают несоответствие типов и пытаются найти способ преобразования объекта funMusic в объект SmartPtr<MusicProduct>. Они ищут в классе SmartPtr<MusicProduct> конструктор с единственным аргументом типа SmartPtr<Cassette> (см. правило 5), но не находят его. Затем пытаются обнаружить в классе SmartPtr<Cassette> оператор неявного преобразования типов, дающий класс SmartPtr<MusicProduct>, но этот поиск также заканчивается неудачей. Потом компиляторы ищут шаблон функции-члена, экземпляр которой могут создать, и выясняют, что шаблон внутри SmartPtr <Cassette>, если связать параметр newType с MusicProduct, генерирует нужную функцию. Тогда они создают эту функцию, давая в реаультате следующий код:

SmartPtr<Cassette>::operator SmartPtr<MusicProduct>() {

return SmartPtr<MusicProduct>(pointee);

Будет ли этот код компилироваться? В нем не происходит ничего, кроме вызова конструктора SmartPtr<MusicProduct> с аргументом pointee, поэтому вопрос сводится к тому, можно ли создать объект SmartPtr<MusicProduct> при помощи указателя Cassette*. Конструктор SmartPtr<MusicProduct> ожидает указатель MusicProduct*, но теперь есть твердое основание для сравнения между двумя типами обычных указателей, и ясно, что можно подставить Cassette* там, где ожидается MusicProduct*. Поэтому создание объекта



1 ... 54 55 56 [ 57 ] 58 59 60 ... 96

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