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

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. Поэтому лучшей из устоявших будет функция release(const Bear&):

extern void release( const ZooAnimal& );

extern void release( const Bear& );

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

release( yinYang );

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

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

void receive( void* );

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( yinYang ) ); // правильно

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

extern void release( const Bear& );

extern void release( const Panda& );

ZooAnimal za;

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

release( 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* )

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;

// ...

};

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

MI *pi = new MI;

(a) pi-print(); (c) pi-readOn(); (e) pi-log( num );

(b) pi-debug(); (d) pi-reset(0); (f) pi-writeOn();

Упражнение 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;

operate( *pd );

(b) void calc( int );

void calc( double );

void calc( const Derived & );

Base *pb = new Derived;

operate( *pb );

2012-11-03 22:30:41 Тарас

Супер статья! с первого раза конечно не до конца прочитал, но сохраню. Спасибо автору.

2012-10-28 16:29:45 СчаСтливыЙ

Дмитрий, Вы не где не найдёте учебник по С++ на одних примерах, или как вы варазились с меньшим текстом. Практика освона на теории. Учебник хорош, доступным языком написано, но примеров серьёзно мало, приходится искать в и-нете примеры. Хотя какая то работа, не всё же предоставлять пользователям.)

2011-10-24 12:10:43 Дмитрий

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