С++ для начинающих


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


Наследование влияет и на третий шаг разрешения перегрузки – выбор наилучшей из устоявших функций. На этом шаге ранжируются преобразования типов, с помощью которых можно привести фактические аргументы функции к типам соответственных формальных параметров. Следующие неявные преобразования имеют тот же ранг, что и стандартные (стандартные преобразования рассматривались в разделе 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< Bear >( 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 );


    Содержание раздела