pc1 = calc;
// ошибка: нет соответствия: неверный тип второго параметра
pc2 = calc;
9.1.7. Безопасное связывание A
При использовании перегрузки складывается впечатление, что в программе можно иметь несколько одноименных функций с разными списками параметров. Однако это лексическое удобство существует только на уровне исходного текста. В большинстве систем компиляции программы, обрабатывающие этот текст для получения исполняемого кода, требуют, чтобы все имена были различны. Редакторы связей, как правило, разрешают внешние ссылки лексически. Если такой редактор встречает имя print два или более раз, он не может различить их путем анализа типов (к этому моменту информация о типах обычно уже потеряна). Поэтому он просто печатает сообщение о повторно определенном символе print и завершает работу.
Чтобы разрешить эту проблему, имя функции вместе с ее списком параметров декорируется так, чтобы получилось уникальное внутреннее имя. Вызываемые после компилятора программы видят только это внутреннее имя. Как именно производится такое преобразование имен, зависит от реализации. Общая идея заключается в том, чтобы представить число и типы параметров в виде строки символов и дописать ее к имени функции.
Как было сказано в разделе 8.2, такое кодирование гарантирует, в частности, что два объявления одноименных функций с разными списками параметров, находящиеся в разных файлах, не воспринимаются редактором связей как объявления одной и той же функции. Поскольку этот способ помогает различить перегруженные функции на фазе редактирования связей, мы говорим о безопасном связывании.
Декорирование имен не применяется к функциям, объявленным с помощью директивы extern "C", так как лишь одна из множества перегруженных функций может быть написана на чистом С. Две функции с различными списками параметров, объявленные как extern "C", редактор связей воспринимает как один и тот же символ.
Зачем может понадобиться объявлять перегруженные функции?
Как нужно объявить перегруженные варианты функции error(), чтобы были корректны следующие вызовы:
int index;
int upperBound;
char selectVal;
// ...
error( "Array out of bounds: ", index, upperBound );
error( "Division by zero" );
error( "Invalid selection", selectVal );
Объясните, к какому эффекту приводит второе объявление в каждом из приведенных примеров:
(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 );
Какая из следующих инициализаций приводит к ошибке? Почему?
(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++. Пытаясь разобраться во всех деталях, начинающие программисты столкнутся с серьезными трудностями. Поэтому в данном разделе мы представим лишь краткий обзор того, как происходит разрешение перегрузки, чтобы у вас составилось хоть какое-то впечатление об этом процессе. Для тех, кто хочет узнать больше, в следующих двух разделах приводится более подробное описание.
Процесс разрешения перегрузки функции состоит из трех шагов, которые мы покажем на следующем примере:
void f();
void f( int );
void f( double, double = 3.4 );
void f( char *, char * );
void main() {
f( 5.6 );
return 0;
}
При разрешении перегрузки функции выполняются следующие шаги: