Наилучшая из устоявших функций
Наследование влияет и на третий шаг разрешения перегрузки – выбор наилучшей из устоявших функций. На этом шаге ранжируются преобразования типов, с помощью которых можно привести фактические аргументы функции к типам соответственных формальных параметров. Следующие неявные преобразования имеют тот же ранг, что и стандартные (стандартные преобразования рассматривались в разделе 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. Поэтому лучшей из устоявших будет функция release(const Bear&):
extern void release( const ZooAnimal& ); extern void release( const Bear& ); // правильно: release( const Bear& ) |
Аналогичное правило применимо и к указателям. При ранжировании стандартных преобразований из указателя на тип производного класса в указатели на типы различных базовых лучшим считается то, для которого базовый класс наименее удален от производного. Это правило распространяется и на тип void*.
Стандартное преобразование в указатель на тип любого базового класса всегда лучше, чем преобразование в void*. Например, если дана пара перегруженных функций:
void receive( void* ); |
то наилучшей из устоявших для вызова с аргументом типа 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( static_cast< Bear >( yinYang ) ); // правильно
Инициализация объекта производного класса или ссылки на него объектом типа базового, а также преобразование указателя на тип базового класса в указатель на тип производного никогда не выполняются компилятором неявно. (Однако их можно выполнить с помощью явного применения dynamic_cast, как мы видели в разделе 19.1.) Для данного вызова не существует наилучшей из устоявших функции, так как нет неявного преобразования аргумента типа ZooAnimal в тип производного класса:
extern void release( const Bear& ); extern void release( const Panda& ); ZooAnimal za; // ошибка: нет соответствия |
В следующем примере наилучшей из устоявших будет release(const char*). Это может показаться удивительным, так как к аргументу применена последовательность пользовательских преобразований, в которой участвует конвертер const char*(). Но поскольку неявного приведения от типа базового класса к типу производного не существует, то release(const Bear&) не является устоявшей функцией, так что остается только release(const char*):
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* ) |
Упражнение 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; // ... |
};
Какие функции входят в множество кандидатов для каждого из следующих вызовов:
MI *pi = new MI; (a) pi->print(); (c) pi->readOn(); (e) pi->log( num ); |
Упражнение 19.10
Дана такая иерархия классов:
class Base { public: operator int(); operator const char *(); // ... }; class Derived : public Base { public: operator double(); // ... |
Удастся ли выбрать наилучшую из устоявших функций для каждого из следующих вызовов? Назовите кандидаты, устоявшие функции и преобразования типов аргументов для каждой из них, наилучшую из устоявших (если она есть):
(a) void operate( double ); void operate( string ); void operate( const Base & ); Derived *pd = new Derived; |
(b) void calc( int ); void calc( double ); void calc( const Derived & ); Base *pb = new Derived; |
20