Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

19.3.2. Устоявшие функции и последовательности пользовательских преобразований

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

В разделе 15.9 мы показали, как разработчик класса может предоставить пользовательские преобразования для объектов этого класса, которые неявно вызываются компилятором для трансформации фактического аргумента функции в тип соответственного формального параметра. Пользовательские преобразования бывают двух видов: конвертер или конструктор с одним параметром без ключевого слова explicit. При наследовании на втором шаге разрешения перегрузки рассматривается более широкое множество таких преобразований.

Конвертеры наследуются, как и любые другие функции-члены класса. Например, мы

class ZooAnimal { public:

// конвертер: ZooAnimal ==> const char*

operator const char*();

// ...

можем написать следующий конвертер для ZooAnimal:

};

Производный класс Bear наследует его от своего базового ZooAnimal. Если значение типа Bear используется в контексте, где ожидается const char*, то неявно вызывается

extern void display( const char* );

Bear yogi;

// правильно: yogi ==> const char*

конвертер для преобразования Bear в const char*: display( yogi );

Конструкторы с одним аргументом без ключевого слова explicit образуют другое множество неявных преобразований: из типа параметра в тип своего класса. Определим такой конструктор для ZooAnimal:

class ZooAnimal { public:

// преобразование: int ==> ZooAnimal

ZooAnimal( int );

// ...

};

Его можно использовать для приведения значения типа int к типу ZooAnimal. Однако конструкторы не наследуются. Конструктор ZooAnimal нельзя применять для

const int cageNumber = 8788l void mumble( const Bear & );

// ошибка: ZooAnimal( int ) не используется

преобразования объекта в случае, когда целевым является тип производного класса: mumble( cageNumber );

Поскольку целевым типом является Bear – тип параметра функции mumble(), то рассматриваются только его конструкторы.

19.3.3. Наилучшая из устоявших функций

Наследование влияет и на третий шаг разрешения перегрузки – выбор наилучшей из устоявших функций. На этом шаге ранжируются преобразования типов, с помощью которых можно привести фактические аргументы функции к типам соответственных формальных параметров. Следующие неявные преобразования имеют тот же ранг, что и стандартные (стандартные преобразования рассматривались в разделе 9.3):

преобразование аргумента типа производного класса в параметр типа любого из его базовых;

преобразование указателя на тип производного класса в указатель на тип любого из его базовых;

инициализация ссылки на тип базового класса с помощью l-значения типа производного.

Они не являются пользовательскими, так как не зависят от конвертеров и конструкторов,

extern void release( const ZooAnimal& ); Panda yinYang;

// стандартное преобразование: Panda -> ZooAnimal

имеющихся в классе:

release( yinYang );

Поскольку аргумент yinYang типа Panda инициализирует ссылку на тип базового класса, то преобразование имеет ранг стандартного.

В разделе 15.10 мы говорили, что стандартные преобразования имеют более высокий

class Panda : public Bear, public Endangered

{

// наследует ZooAnimal::operator const char *()

};

Panda yinYang;

extern void release( const ZooAnimal& ); extern void release( const char * );

//стандартное преобразование: Panda -> ZooAnimal

//выбирается: release( const ZooAnimal& )

ранг, чем пользовательские: release( yinYang );

Как release(const char*), так и release(ZooAnimal&) являются устоявшими функциями: первая потому, что инициализация параметра-ссылки значением аргумента – стандартное преобразование, а вторая потому, что аргумент можно привести к типу const char* с помощью конвертера ZooAnimal::operator const char*(), который представляет собой пользовательское преобразование. Так как стандартное преобразование лучше пользовательского, то в качестве наилучшей из устоявших выбирается функция release(const ZooAnimal&).

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

extern void release( const ZooAnimal& );

extern void release( const Bear& );

// правильно: release( const Bear& )

устоявших будет функция release(const Bear&): release( yinYang );

Аналогичное правило применимо и к указателям. При ранжировании стандартных преобразований из указателя на тип производного класса в указатели на типы различных базовых лучшим считается то, для которого базовый класс наименее удален от производного. Это правило распространяется и на тип void*.

Стандартное преобразование в указатель на тип любого базового класса всегда лучше, чем преобразование в void*. Например, если дана пара перегруженных функций:

void

receive( v oid* );

void receive( ZooAnimal* );

то наилучшей из устоявших для вызова с аргументом типа Panda* будет receive(ZooAnimal*).

В случае множественного наследования два стандартных преобразования из типа производного класса в разные типы базовых могут иметь одинаковый ранг, если оба базовых класса равноудалены от производного. Например, Panda наследует классам Bear и Endangered. Поскольку они равноудалены от производного Panda, то преобразования объекта Panda в любой из этих классов одинаково хороши. Но тогда единственной наилучшей из устоявших функции для следующего вызова не существует,

extern void mumble( const Bear& ); extern void mumble( const Endangered& );

/* ошибка: неоднозначный вызов:

*может быть выбрана любая из двух функций

*void mumble( const Bear& );

* void mumble( const Endangered& );

*/

и он считается ошибочным: mumble( yinYang );

Для разрешения неоднозначности программист может применить явное приведение типа:

mumble( static_cast< Bear >( yinYang ) ); // правильно

Инициализация объекта производного класса или ссылки на него объектом типа базового, а также преобразование указателя на тип базового класса в указатель на тип производного никогда не выполняются компилятором неявно. (Однако их можно выполнить с помощью явного применения dynamic_cast, как мы видели в разделе 19.1.) Для данного вызова не существует наилучшей из устоявших функции, так как нет

extern void release( const Bear& ); extern void release( const

Panda& );

ZooAnimal za;

// ошибка: нет соответствия

неявного преобразования аргумента типа ZooAnimal в тип производного класса: release( za );

В следующем примере наилучшей из устоявших будет release(const char*). Это может показаться удивительным, так как к аргументу применена последовательность пользовательских преобразований, в которой участвует конвертер const char*(). Но поскольку неявного приведения от типа базового класса к типу производного не

существует, то release(const Bear&) не является устоявшей функцией, так что

Class ZooAnimal { public:

// преобразование: ZooAnimal ==> const char*

operator const char*();

// ...

};

extern void release( const char* ); extern void release( const Bear& );

ZooAnimal za;

//za ==> const char*

//правильно: release( const char* )

остается только release(const char*):

release( za );Ошибка! Закладка не определена.Ошибка! Закладка не определена.Ошибка! Закладка не определена.

Упражнение 19.9

class Base1 { public:

ostream& print(); void debug(); void writeOn();

void log( string ); void reset( void *); // ...

};

class Base2 { public:

void debug(); void readOn();

void log( double ); // ...

};

class MI : public Base1, public Base2

{

public:

ostream& print(); using Base1::reset; void reset( char * ); using Base2::log; using Base2::log;

// ...

Дана такая иерархия классов:

};

Какие функции входят в множество кандидатов для каждого из следующих вызовов: