Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

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

432

9.1.5. Директива extern "C" и перегруженные функции A

Вразделе 7.7 мы видели, что директиву связывания extern "C" можно использовать в программе на C++ для того, чтобы указать, что некоторый объект находится в части, написанной на языке C. Как эта директива влияет на объявления перегруженных функций? Могут ли в одном и том же множестве находиться функции, написанные как на C++, так и на C?

Вдирективе связывания разрешается задать только одну из множества перегруженных

// ошибка: для двух перегруженных функций указана директива extern "C" extern "C" void print( const char* );

функций. Например, следующая программа некорректна: extern "C" void print( int );

Приведенный ниже пример перегруженной функции calc() иллюстрирует типичное

class SmallInt ( /* ... */ ); class BigNum ( /* ... */ );

//написанная на C функция может быть вызвана как из программы,

//написанной на C, так и из программы, написанной на C++.

//функции C++ обрабатывают параметры, являющиеся классами extern "C" double calc( double );

extern SmallInt calc( const SmallInt& );

применение директивы extern "C":

extern BigNum calc( const BigNum& );

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

Директива связывания не имеет значения при решении, какую функцию вызывать; важны только типы параметров. Выбирается та функция, которая лучше всего

Smallint si = 8;

 

int main() {

// вызывается C-функция calc( double )

calc( 34 );

calc( si );

// вызывается функция C++ calc( const SmallInt & )

// ...

 

return 0;

 

соответствует типам переданных аргументов:

}

9.1.6. Указатели на перегруженные функции A

Можно объявить указатель на одну из множества перегруженных функций. Например:

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

433

extern void ff( vector<double> ); extern void ff( unsigned int );

// на какую функцию указывает pf1?

void ( *pf1 )( unsigned int ) = &ff;

Поскольку функция ff() перегружена, одного инициализатора &ff недостаточно для выбора правильного варианта. Чтобы понять, какая именно функция инициализирует указатель, компилятор ищет в множестве всех перегруженных функций ту, которая имеет тот же тип возвращаемого значения и список параметров, что и функция, на которую ссылается указатель. В нашем случае будет выбрана функция ff(unsigned int).

А что если не найдется функции, в точности соответствующей типу указателя? Тогда

extern void ff( vector<double> ); extern void ff( unsigned int );

// ошибка: соответствие не найдено: неверный список параметров

компилятор выдаст сообщение об ошибке:

// ошибка: соответствие не найдено: неверный тип возвращаемого значения

void ( *pf2 )( int ) = &ff;

double ( *pf3 )( vector<double> ) = &ff;

Присваивание работает аналогично. Если значением указателя должен стать адрес перегруженной функции , то для выбора операнда в правой части оператора присваивания используется тип указателя на функцию. И если компилятор не находит функции, в точности соответствующей нужному типу, он выдает сообщение об ошибке. Таким образом, преобразование типов между указателями на функции никогда не

matrix calc( const matrix & ); int calc( int, int );

int ( *pc1 )( int, int ) = 0; int ( *pc2 )( int, double ) = 0;

//...

//правильно: выбирается функция calc( int, int ) pc1 = &calc;

//ошибка: нет соответствия: неверный тип второго параметра

производится. pc2 = &calc;

9.1.7. Безопасное связывание A

При использовании перегрузки складывается впечатление, что в программе можно иметь несколько одноименных функций с разными списками параметров. Однако это лексическое удобство существует только на уровне исходного текста. В большинстве систем компиляции программы, обрабатывающие этот текст для получения исполняемого

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

434

кода, требуют, чтобы все имена были различны. Редакторы связей, как правило, разрешают внешние ссылки лексически. Если такой редактор встречает имя print два или более раз, он не может различить их путем анализа типов (к этому моменту информация о типах обычно уже потеряна). Поэтому он просто печатает сообщение о повторно определенном символе print и завершает работу.

Чтобы разрешить эту проблему, имя функции вместе с ее списком параметров декорируется так, чтобы получилось уникальное внутреннее имя. Вызываемые после компилятора программы видят только это внутреннее имя. Как именно производится такое преобразование имен, зависит от реализации. Общая идея заключается в том, чтобы

представить число и типы параметров в виде строки символов и дописать ее к имени функции.

Как было сказано в разделе 8.2, такое кодирование гарантирует, в частности, что два объявления одноименных функций с разными списками параметров, находящиеся в разных файлах, не воспринимаются редактором связей как объявления одной и той же функции. Поскольку этот способ помогает различить перегруженные функции на фазе редактирования связей, мы говорим о безопасном связывании.

Декорирование имен не применяется к функциям, объявленным с помощью директивы 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

Объясните, к какому эффекту приводит второе объявление в каждом из приведенных примеров: