С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 73
Текст из файла (страница 73)
Поскольку этот способ помогает различить перегруженные функции на фазередактирования связей, мы говорим о безопасном связывании.Декорирование имен не применяется к функциям, объявленным с помощью директивыextern "C", так как лишь одна из множества перегруженных функций может бытьнаписана на чистом С. Две функции с различными списками параметров, объявленныекак extern "C", редактор связей воспринимает как один и тот же символ.Упражнение 9.1Зачем может понадобиться объявлять перегруженные функции?Упражнение 9.2Как нужно объявить перегруженные варианты функции error(), чтобы были корректныint index;int upperBound;char selectVal;// ...error( "Array out of bounds: ", index, upperBound );error( "Division by zero" );следующие вызовы:error( "Invalid selection", selectVal );Упражнение 9.3Объясните, к какому эффекту приводит второе объявление в каждом из приведенныхпримеров:434С++ для начинающих(a) int calc( int, int );int calc( const int, const int );(b) int get();double get();(c) int *reset( int * );double *reset( double * ):(d) extern "C" int compute( int *, int );extern "C" double compute( double *, double );Упражнение 9.4(a) void reset( int * );void (*pf)( void * ) = reset;(b) int calc( int, int );int (*pf1)( int, int ) = calc;(c) extern "C" int compute( int *, int );int (*pf3)( int*, int ) = compute;Какая из следующих инициализаций приводит к ошибке? Почему?(d) void (*pf4)( const matrix & ) = 0;9.2.
Три шага разрешения перегрузкиРазрешением перегрузки функции называется процесс выбора той функции из множестваперегруженных, которую следует вызвать. Этот процесс основывается на указанных приT t1, t2;void f( int, int );void f( float, float );int main() {f( t1, t2 );return 0;вызове аргументах. Рассмотрим пример:}Здесь в ходе процесса разрешения перегрузки в зависимости от типа T определяется,будет ли при обработке выражения f(t1,t2) вызвана функция f(int,int) илиf(float,float) или зафиксируется ошибка.Разрешение перегрузки функции – один и самых сложных аспектов языка C++. Пытаясьразобраться во всех деталях, начинающие программисты столкнутся с серьезнымитрудностями. Поэтому в данном разделе мы представим лишь краткий обзор того, какпроисходит разрешение перегрузки, чтобы у вас составилось хоть какое-то впечатление435С++ для начинающихоб этом процессе. Для тех, кто хочет узнать больше, в следующих двух разделахприводится более подробное описание.Процесс разрешения перегрузки функции состоит из трех шагов, которые мы покажем наvoidvoidvoidvoidf();f( int );f( double, double = 3.4 );f( char *, char * );void main() {f( 5.6 );return 0;следующем примере:}При разрешении перегрузки функции выполняются следующие шаги:1.
Выделяется множество перегруженных функций для данного вызова, а такжесвойства списка аргументов, переданных функции.2. Выбираются те из перегруженных функций, которые могут быть вызваны с даннымиаргументами, с учетом их количества и типов.3. Находится функция, которая лучше всего соответствует вызову.Рассмотрим последовательно каждый пункт.На первом шаге необходимо идентифицировать множество перегруженных функций,которые будут рассматриваться при данном вызове. Вошедшие в это множество функцииназываются кандидатами.
Функция-кандидат – это функция с тем же именем, что ивызванная, причем ее объявление видимо в точке вызова. В нашем примере есть четыретаких кандидата: f(), f(int), f(double, double) и f(char*, char*).После этого идентифицируются свойства списка переданных аргументов, т.е. ихколичество и типы. В нашем примере список состоит из двух аргументов типа double.На втором шаге среди множества кандидатов отбираются устоявшие (viable) – такие,которые могут быть вызваны с данными аргументами, Устоявшая функция либо имеетстолько же формальных параметров, сколько фактических аргументов передановызванной функции, либо больше, но тогда для каждого дополнительного параметрадолжно быть задано значение по умолчанию. Чтобы функция считалась устоявшей, длялюбого фактического аргумента, переданного при вызове, обязано существоватьпреобразование к типу формального параметра, указанного в объявлении.В нашем примере есть две устоявших функции, которые могут быть вызваны сприведенными аргументами:•функция f(int) устояла, потому что у нее есть всего один параметр исуществует преобразование фактического аргумента типа double к формальномупараметру типа int;•функция f(double,double) устояла, потому что для второго аргумента естьзначение по умолчанию, а первый формальный параметр имеет тип double, что вточности соответствует типу фактического аргумента.Если после второго шага не нашлось устоявших функций, то вызов считаетсяошибочным.
В таких случаях мы говорим, что имеет место отсутствие соответствия.436С++ для начинающихТретий шаг заключается в выборе функции, лучше всего отвечающей контексту вызова.Такая функция называется наилучшей из устоявших (или наиболее подходящей). На этомшаге производится ранжирование преобразований, использованных для приведениятипов фактических аргументов к типам формальных параметров устоявшей функции.Наиболее подходящей считается функция, для которой выполняются следующие условия:преобразования, примененные к фактическим аргументам, не хуже преобразований,необходимых для вызова любой другой устоявшей функции;для некоторых аргументов примененные преобразования лучше, чем преобразования,необходимые для приведения тех же аргументов в вызове других устоявших функций.Преобразования типов и их ранжирование более подробно обсуждаются в разделе 9.3.Здесь мы лишь кратко рассмотрим ранжирование преобразований для нашего примера.Для устоявшей функции f(int) должно быть применено приведение фактическогоаргумента типа double к типу int, относящееся к числу стандартных.
Для устоявшейфункции f(double,double) тип фактического аргумента double в точностисоответствует типу формального параметра. Поскольку точное соответствие лучшестандартного преобразования (отсутствие преобразования всегда лучше, чем егоналичие), то наиболее подходящей функцией для данного вызова считаетсяf(double,double).Если на третьем шаге не удается отыскать единственную лучшую из устоявших функцию,иными словами, нет такой устоявшей функции, которая подходила бы больше всехостальных, то вызов считается неоднозначным, т.е. ошибочным.(Более подробно все шаги разрешения перегрузки функции обсуждаются в разделе 9.4.Процесс разрешения используется также при вызовах перегруженной функции-членакласса и перегруженного оператора. В разделе 15.10 рассматриваются правиларазрешения перегрузки, применяемые к функциям-членам класса, а в разделе 15.11 –правила для перегруженных операторов.
При разрешении перегрузки следует такжепринимать во внимание функции, конкретизированные из шаблонов. В разделе 10.8обсуждается, как шаблоны влияют на такое разрешение.)Упражнение 9.5Что происходит на последнем (третьем) шаге процесса разрешения перегрузки функции?9.3. Преобразования типов аргументов AНа втором шаге процесса разрешения перегрузки функции компилятор идентифицирует иранжирует преобразования, которые следует применить к каждому фактическомуаргументу вызванной функции для приведения его к типу соответствующегоформального параметра любой из устоявших функций.
Ранжирование может дать одиниз трех возможных результатов:•точное соответствие. Тип фактического аргумента точно соответствует типуформального параметра. Например, если в множестве перегруженных функцийvoid print( unsigned int );void print( const char* );print() есть такие:void print( char );437С++ для начинающих438unsigned int a;print( 'a' );print( "a" );// соответствует print( char );// соответствует print( const char* );то каждый из следующих трех вызовов дает точное соответствие:print( a );•// соответствует print( unsigned int );соответствие с преобразованием типа.
Тип фактического аргумента неvoid ff( char );соответствует типу формального параметра, но может быть преобразован в него:ff( 0 );•// аргумент типа int приводится к типу charотсутствие соответствия. Тип фактического аргумента не может бытьприведен к типу формального параметра в объявлении функции, посколькунеобходимого преобразования не существует. Для каждого из следующих двух// функции print() объявлены так же, как и вышеint *ip;class SmallInt { /* ... */ };SmallInt si;print( ip );// ошибка: нет соответствиявызовов функции print() соответствия нет:print( si );// ошибка: нет соответствияДля установления точного соответствия тип фактического аргумента необязательнодолжен совпадать с типом формального параметра.