Главная страница
Навигация по странице:

  • 15.11.2. Функции-кандидаты

  • 15.11.3. Устоявшие функции

  • 15.12.1. Операторные функции-кандидаты

  • 15.12.2. Устоявшие функции

  • Язык программирования C++. Вводный курс. С для начинающих


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница73 из 93
    1   ...   69   70   71   72   73   74   75   76   ...   93
    763
    };
    Все функции из множества перегруженных должны быть объявлены в одной и той же области видимости. Поэтому функции-члены никогда не перегружают функций, объявленных в пространстве имен. Кроме того, поскольку у каждого класса своя область видимости, функции, являющиеся членами разных классов, не перегружают друг друга.
    Множество перегруженных функций-членов может содержать как статические, так и нестатические функции:
    };
    Какая из функций-членов будет вызвана – статическая или нестатическая – зависит от результатов разрешения перегрузки. Процесс разрешения в ситуации, когда устояли как статические, так и нестатические члены, мы подробно рассмотрим в следующем разделе.
    15.11.2.
    Функции-кандидаты
    Рассмотрим два вида вызовов функции-члена: pmc->mf( arg ); где mc – выражение типа myClass, а pmc – выражение типа “указатель на тип myClass”.
    Множество кандидатов для обоих вызовов составлено из функций, найденных в области видимости класса myClass при поиске объявления mf().
    Аналогично для вызова функции вида myClass::mf( arg ); множество кандидатов также состоит из функций, найденных в области видимости класса myClass при поиске объявления mf(). Например: class myClass { public: void mcf( double ); static void mcf( int* ); // перегружает myClass::mcf( double )
    // ... mc.mf( arg );

    С++ для начинающих
    764
    }
    Кандидатами для вызова функции в main() являются все три функции-члена mf(), объявленные в myClass: static void mf( int* );
    Если бы в myClass не было объявлено ни одной функции-члена с именем mf(), то множество кандидатов оказалось бы пустым. (На самом деле рассматривались бы также и функции из базовых классов. О том, как они попадают в это множество, мы поговорим в разделе 19.3.) Если для вызова функции не оказывается кандидатов, компилятор выдает сообщение об ошибке.
    15.11.3.
    Устоявшие функции
    Устоявшей называется функция из множества кандидатов, которая может быть вызвана с данными фактическими аргументами. Чтобы она устояла, должны существовать неявные преобразования между типами фактических аргументов и формальных параметров.
    Например:
    }
    В этом фрагменте для вызова mf() из main() есть две устоявшие функции: class myClass { public: void mf( double ); void mf( char, char = '\n' ); static void mf( int* );
    // ...
    }; int main() { myClass mc; int iobj; mc.mf( iobj ); void mf( double ); void mf( char, char = '\n' ); class myClass { public: void mf( double ); void mf( char, char = '\n' ); static void mf( int* );
    // ...
    }; int main() { myClass mc; int iobj; mc.mf( iobj ); // какая именно функция-член mf()? Неоднозначно

    С++ для начинающих
    765
    void mf( char, char = '\n' );

    mf(double)
    устояла потому, что у нее только один параметр и существует стандартное преобразование аргумента iobj типа int в параметр типа double;

    mf(char,char)
    устояла потому, что для второго параметра имеется значение по умолчанию и существует стандартное преобразование аргумента iobj типа int в тип char первого формального параметра.
    При выборе наилучшей из устоявших функции преобразования типов, применяемые к каждому фактическому аргументу, ранжируются. Лучшей считается та, для которое все использованные преобразования не хуже, чем для любой другой устоявшей функции, и хотя бы для одного аргумента такое преобразование лучше, чем для всех остальных функций.
    В предыдущем примере в каждой из двух устоявших функций для приведения типа фактического аргумента к типу формального параметра применено стандартное преобразование. Вызов считается неоднозначным, так как обе функции-члена разрешают его одинаково хорошо.
    Независимо от вида вызова функции, в множество устоявших могут быть включены как статические, так и нестатические члены:
    }
    Здесь функция-член mf() вызывается с указанием имени класса и оператора разрешения области видимости myClass::mf(). Однако не задан ни объект (с оператором “точка”), ни указатель на объект (с оператором “стрелка”). Несмотря на это, нестатическая функция-член mf(char) все же включается в множество устоявших наряду со статическим членом mf(int).
    Затем процесс разрешения перегрузки продолжается: на основе ранжирования преобразований типов, примененных к фактическим аргументам, чтобы выбрать наилучшую из устоявших функций. Аргумент cobj типа char точно соответствует формальному параметру mf(char) и может быть расширен до типа формального параметра mf(int). Поскольку ранг точного соответствия выше, то выбирается функция mf(char)
    Однако эта функция-член не является статической и, следовательно, вызывается только через объект или указатель на объект класса myClass с помощью одного из операторов доступа. В такой ситуации, если объект не указан и, значит, вызов функции невозможен
    (как раз наш случай), компилятор считает его ошибкой. void mf( double ); class myClass { public: static void mf( int ); char mf( char );
    }; int main() { char cobj; myClass::mf( cobj ); // какая именно функция-член?

    С++ для начинающих
    766
    Еще одна особенность функций-членов, которую надо принимать во внимание при формировании множества устоявших функций, – это наличие спецификаторов const или volatile у нестатических членов. (Они рассматривались в разделе 13.3.) Как они влияют на процесс разрешения перегрузки? Пусть в классе myClass есть следующие функции-члены:
    };
    Тогда и статическая функция-член mf(int*), и константная функция mf(int), и неконстантная функция mf(double) включаются в множество кандидатов для показанного ниже вызова. Но какие из них войдут в множество устоявших?
    }
    Исследуя преобразования, которые надо применить к фактическим аргументам, мы обнаруживаем, что устояли функции mf(double) и mf(int). Тип double фактического аргумента dobj точно соответствует типу формального параметра mf(double) и может быть приведен к типу параметра mf(int) с помощью стандартного преобразования.
    Если при вызове функции-члена используются операторы доступа “точка” или “стрелка”, то при отборе функций в множество устоявших принимается во внимание тип объекта или указателя, для которого вызвана функция. mc
    – это константный объект, для которого можно вызывать только нестатические константные функции-члены. Следовательно, неконстантная функция-член mf(double) исключается из множества устоявших, и остается в нем единственная функция mf(int), которая и вызывается.
    А если константный объект использован для вызова статической функции-члена? Ведь для такой функции нельзя задавать спецификатор const или volatile, так можно ли ее вызывать через константный объект? class myClass { public: static void mf( int* ); void mf( double ); void mf( int ) const;
    // ... int main() { const myClass mc; double dobj; mc.mf( dobj ); // какая из функций-членов mf()? class myClass { public: static void mf( int ); char mf( char );
    }; int main() { const myClass mc; int iobj; mc.mf( iobj ); // можно ли вызывать статическую функцию-член?

    С++ для начинающих
    767
    }
    Статические функции-члены являются общими для всех объектов одного класса.
    Напрямую они могут обращаться только к статическим членам класса. Так, нестатические члены константного объекта mc недоступны статической mf(int). По этой причине разрешается вызывать статическую функцию-член для константного объекта с помощью операторов “точка” или “стрелка”.
    Таким образом, статические функции-члены не исключаются из множества устоявших и при наличии спецификаторов const или volatile у объекта, для которого они вызваны.
    Статические функции-члены рассматриваются как соответствующие любому объекту или указателю на объект своего класса.
    В примере выше mc – константный объект, поэтому функция-член mf(char) исключается из множества устоявших. Но функция-член mf(int) в нем остается, так как является статической. Поскольку это единственная устоявшая функция, она и оказывается наилучшей.
    15.12.
    Разрешение перегрузки и операторы A
    В классах могут быть объявлены перегруженные операторы и конвертеры. Предположим, при инициализации встретился оператор сложения: int iobj = sc + 3;
    Как компилятор решает, что следует сделать: вызвать перегруженный оператор для класса SomeClass или конвертировать операнд sc во встроенный тип, а затем уже воспользоваться встроенным оператором?
    Ответ зависит от множества перегруженных операторов и конвертеров, определенных в
    SomeClass
    . При выборе оператора для выполнения сложения применяется процесс разрешения перегрузки функции. В данном разделе мы расскажем, как этот процесс позволяет выбрать нужный оператор, когда операндами являются объекты типа класса.
    При разрешении перегрузки используется все та же процедура из трех шагов, представленная в разделе 9.2:
    1. Отбор функций-кандидатов.
    2. Отбор устоявших функций.
    3. Выбор наилучшей из устоявших функции.
    Рассмотрим эти шаги более детально.
    Разрешение перегрузки функции не применяется, если все операнды имеют встроенные типы. В таком случае гарантированно употребляется встроенный оператор.
    (Использование операторов с операндами встроенных типов описано в главе 4.)
    Например:
    SomeClass sc;

    С++ для начинающих
    768
    }
    Поскольку операнды i1 и i2 имеют тип int, а не тип класса, то при сложении используется встроенный оператор +. Перегруженный operator+(const SmallInt &, const SmallInt &)
    игнорируется, хотя операнды можно привести к типу SmallInt с помощью определенного пользователем преобразования в виде конструктора
    SmallInt(int)
    . Описанный ниже процесс разрешения перегрузки в таких ситуациях не применяется.
    Кроме того, разрешение перегрузки для операторов употребляется только в случае использования операторного синтаксиса:
    }
    Если вместо этого использовать синтаксис вызова функции: int res = operator+( si, iobj ); // синтаксис вызова функции то применяется процедура разрешения перегрузки для функций в пространстве имен (см. раздел 15.10). Если же использован синтаксис вызова функции-члена: int res = si.operator+( iobj ); то работает соответствующая процедура для функций-членов (см. раздел 15.11).
    15.12.1.
    Операторные функции-кандидаты
    Операторная функция является кандидатом, если она имеет то же имя, что и вызванная.
    При использовании следующего оператора сложения int res = si + iobj; class SmallInt { public:
    SmallInt( int );
    };
    SmallInt operator+ ( const SmallInt &, const SmallInt & ); void func() { int i1, i2; int i3 = i1 + i2; void func() {
    SmallInt si(98); int iobj = 65; int res = si + iobj; // использован операторный синтаксис
    // синтаксис вызова функции-члена
    SmallInt si(98); int iobj = 65;

    С++ для начинающих
    769
    операторной функцией-кандидатом является operator+. Какие объявления operator+ принимаются во внимание?
    Потенциально в случае применения операторного синтаксиса с операндами, имеющими тип класса, строится пять множеств кандидатов. Первые три – те же, что и при вызове обычных функций с аргументами типа класса:

    множество операторов, видимых в точке вызова. Объявления функции operator+()
    , видимые в точке использования оператора, являются кандидатами.
    Например, operator+(), объявленный в глобальной области видимости, – кандидат в случае применения operator+() внутри main():
    }

    множество операторов, объявленных в пространстве имен, в котором определен тип операнда. Если операнд имеет тип класса и этот тип объявлен в пользовательском пространстве имен, то операторные функции, объявленные в том же пространстве и имеющие то же имя, что и использованный оператор, считаются кандидатами:
    }
    Операнд si имеет тип класса SmallInt, объявленного в пространстве имен NS.
    Поэтому перегруженный operator+(const SmallInt, double), объявленный в том же пространстве, добавляется к множеству кандидатов;

    множество операторов, объявленных друзьями классов, к которым принадлежат операнды. Если операнд принадлежит к типу класса и в определении этого класса есть одноименные применяемому оператору функции-друзья, то они добавляются к множеству кандидатов:
    SmallInt operator+ ( const SmallInt &, const SmallInt & ); int main() {
    SmallInt si(98); int iobj = 65; int res = si + iobj; // ::operator+() - функция-кандидат namespace NS { class SmallInt { /* ... */ };
    SmallInt operator+ ( const SmallInt&, double );
    } int main() {
    // si имеет тип SmallInt:
    // этот класс объявлен в пространстве имен NS
    NS::SmallInt si(15);
    // NS::operator+() - функция-кандидат int res = si + 566; return 0;

    С++ для начинающих
    770
    }
    Операнд si имеет тип SmallInt. Операторная функция operator+(const SmallInt&, int)
    , являющаяся другом этого класса, – член пространства имен NS, хотя непосредственно в этом пространстве она не объявлена. При обычном поиске в NS эта операторная функция не будет найдена. Однако при использовании operator+() с аргументом типа SmallInt функции-друзья, объявленные в области видимости этого класса, включаются в рассмотрение и добавляются к множеству кандидатов.
    Эти три множества операторных функций-кандидатов формируются точно так же, как и для вызовов обычных функций с аргументами типа класса. Однако при использовании операторного синтаксиса строятся еще два множества:

    множество операторов-членов, объявленных в классе левого операнда. Если такой операнд оператора operator+() имеет тип класса, то в множество функций- кандидатов включаются объявления operator+(), являющиеся членами этого класса:
    }
    Оператор-член SmallInt::operator+(const myFloat &), определенный в SmallInt, включается в множество функций-кандидатов для разрешения вызова operator+() в main()
    ;

    множество встроенных операторов. Учитывая типы, которые можно использовать со встроенным operator+(), кандидатами являются также: namespace NS { class SmallInt { friend SmallInt operator+( const SmallInt&, int )
    { /* ... */ }
    };
    } int main() {
    NS::SmallInt si(15);
    // функция-друг operator+() - кандидат int res = si + 566; return 0; class myFloat { myFloat( double );
    }; class SmallInt { public:
    SmallInt( int );
    SmallInt operator+ ( const myFloat & );
    }; int main() {
    SmallInt si(15); int res = si + 5.66; // оператор-член operator+() - кандидат

    С++ для начинающих
    771
    T* operator+( I, T* );
    Первое объявление относится к встроенному оператору для сложения двух значений целых типов, второе – к оператору для сложения значений типов с плавающей точкой. Третье и четвертое соответствуют встроенному оператору сложения указательных типов, который используется для прибавления целого числа к указателю. Два последних объявления представлены в символическом виде и описывают целое семейство встроенных операторов, которые могут быть выбраны компилятором на роль кандидатов при обработке операций сложения.
    Любое из первых четырех множеств может оказаться пустым. Например, если среди членов класса SmallInt нет функции с именем operator+(), то четвертое множество будет пусто.
    Все множество операторных функций-кандидатов является объединением пяти подмножеств, описанных выше:
    }
    В эти пять множеств входят семь операторных функций-кандидатов на роль operator+()
    в main():

    первое множество пусто. В глобальной области видимости, а именно в ней употреблен operator+() в функции main(), нет объявлений перегруженного оператора operator+();

    второе множество содержит операторы, объявленные в пространстве имен NS, где определен класс SmallInt. В этом пространстве имеется один оператор:
    NS::SmallInt NS::operator+( const SmallInt &, double ); int operator+( int, int ); double operator+( double, double );
    T* operator+( T*, I ); namespace NS { class myFloat { myFloat( double );
    }; class SmallInt { friend SmallInt operator+( const SmallInt &, int ) { /* ... */ } public:
    SmallInt( int ); operator int();
    SmallInt operator+ ( const myFloat & );
    // ...
    };
    SmallInt operator+ ( const SmallInt &, double );
    } int main() {
    // тип si - class SmallInt:
    //
    Этот класс объявлен в пространстве имен NS
    NS::SmallInt si(15); int res = si + 5.66; // какой operator+()? return 0;

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

    третье множество содержит операторы, объявленные друзьями класса
    SmallInt
    . Сюда входит
    NS::SmallInt NS::operator+( const SmallInt &, int );

    четвертое множество содержит операторы, объявленные членами SmallInt.
    Такой тоже есть:
    NS::SmallInt NS::SmallInt::operator+( const myFloat & );

    пятое множество содержит встроенные бинарные операторы:
    T* operator+( I, T* );
    Да, формирование множества кандидатов для разрешения оператора, использованного с применением операторного синтаксиса, утомительно. Но после того как оно построено, устоявшие функции и наилучшая из них находятся, как и прежде, путем анализа преобразований, применимых к операндам отобранных кандидатов.
    15.12.2.
    Устоявшие функции
    Множество устоявших операторных функций формируется из множества кандидатов путем отбора лишь тех операторов, которые могут быть вызваны с заданными операндами. Например, какие из семи найденных выше кандидатов устоят? Оператор использован в следующем контексте: si + 5.66;
    Левый операнд имеет тип SmallInt, а правый – double.
    Первый кандидат является устоявшей функцией для данного использования operator+()
    :
    NS::SmallInt NS::operator+( const SmallInt &, double );
    Левый операнд типа SmallInt в качестве инициализатора точно соответствует формальному параметру-ссылке этого перегруженного оператора. Правый, имеющий тип double
    , также точно соответствует второму формальному параметру.
    Следующая функция-кандидат также устоит:
    NS::SmallInt NS::operator+( const SmallInt &, int );
    Левый операнд si типа SmallInt в качестве инициализатора точно соответствует формальному параметру-ссылке перегруженного оператора. Правый имеет тип int и int operator+( int, int ); double operator+( double, double );
    T* operator+( T*, I );
    NS::SmallInt si(15);

    С++ для начинающих
    1   ...   69   70   71   72   73   74   75   76   ...   93


    написать администратору сайта