С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 72
Текст из файла (страница 72)
(Функции-членыкласса описываются в главе 13. Разрешение перегрузки для функций-членов классарассматривается в главе 15.)Объявлять такие функции разрешается и внутри пространства имен. С каждым из нихтакже связана отдельная область видимости, так что функции, объявленные в разных#include <string>namespace IBM {extern void print( const string & );extern void print( double );// перегружает print()}namespace Disney {// отдельная область видимости:// не перегружает функцию print() из пространства имен IBMextern void print( int );пространствах, не перегружают друг друга. Например:428С++ для начинающих429}Использование using-объявлений и using-директив помогает сделать члены пространстваимен доступными в других областях видимости. Эти механизмы оказывают определенноевлияние на объявления перегруженных функций.
(Using-объявления и using-директивырассматривались в разделе 8.6.)Каким образом using-объявление сказывается на перегрузке функций? Напомним, что оновводит псевдоним для члена пространства имен в ту область видимости, в которой этоnamespace libs_R_us {int max( int, int );int max( double, double );extern void print( int );extern void print( double );}// using-объявленияusing libs_R_us::max;using libs_R_us::print( double );void func(){max( 87, 65 );// ошибка// вызывает libs_R_us::max( int, int )объявление встречается. Что делают такие объявления в следующей программе?max( 35.5, 76.6 ); // вызывает libs_R_us::max( double, double )Первое using-объявление вводит обе функции libs_R_us::max в глобальную областьвидимости. Теперь любую из функций max() можно вызвать внутри func().
По типамаргументов определяется, какую именно функцию вызывать. Второе using-объявление –это ошибка: в нем нельзя задавать список параметров. Функция libs_R_us::print()объявляется только так:using libs_R_us::print;Using-объявление всегда делает доступными все перегруженные функции с указаннымименем. Такое ограничение гарантирует, что интерфейс пространства имен libs_R_us небудет нарушен.
Ясно, что в случае вызоваprint( 88 );автор пространства имен ожидает, что будет вызвана функция libs_R_us::print(int).Если разрешить пользователю избирательно включать в область видимости лишь одну изнескольких перегруженных функций, то поведение программы становитсянепредсказуемым.Что происходит, если using-объявление вводит в область видимости функцию с ужесуществующим именем? Эти функции выглядят так, как будто они объявлены прямо втом месте, где встречается using-объявление. Поэтому введенные функции участвуют впроцессе разрешения имен всех перегруженных функций, присутствующих в даннойобласти видимости:С++ для начинающих#include <string>namespace libs_R_us {extern void print( int );extern void print( double );}extern void print( const string & );// libs_R_us::print( int ) и libs_R_us::print( double )// перегружают print( const string & )using libs_R_us::print;void fooBar( int ival ){print( "Value: ");// вызывает глобальную функцию// print( const string & )print( ival );// вызывает libs_R_us::print( int )}Using-объявление добавляет в глобальную область видимости два объявления: дляprint(int) и для print(double).
Они являются псевдонимами в пространствеlibs_R_us и включаются в множество перегруженных функций с именем print, где уженаходится глобальная print(const string &). При разрешении перегрузки print вfooBar рассматриваются все три функции.Если using-объявление вводит некоторую функцию в область видимости, в которой ужеимеется функция с таким же именем и таким же списком параметров, это считаетсяошибкой. С помощью using-объявления нельзя задать псевдоним для функцииprint(int) в пространстве имен libs_R_us, если в глобальной области видимости ужеnamespace libs_R_us {void print( int );void print( double );}void print( int );using libs_R_us::print; // ошибка: повторное объявление print(int)void fooBar( int ival ){print( ival );// какая print? ::print или libs_R_us::printесть print(int). Например:}Мы показали, как связаны using-объявления и перегруженные функции.
Теперьрассмотрим особенности применения using-директивы. Using-директива приводит к тому,что члены пространства имен выглядят объявленными вне этого пространства, добавляяих в новую область видимости. Если в этой области уже есть функция с тем же именем,то происходит перегрузка. Например:430С++ для начинающих431#include <string>namespace libs_R_us {extern void print( int );extern void print( double );}extern void print( const string & );// using-директива// print(int), print(double) и print(const string &) - элементы// одного и того же множества перегруженных функцийusing namespace libs_R_us;void fooBar( int ival ){print( "Value: ");// вызывает глобальную функцию// print( const string & )print( ival );// вызывает libs_R_us::print( int )}Это верно и в том случае, когда есть несколько using-директив.
Одноименные функции,namespace IBM {int print( int );}namespace Disney {double print( double );являющиеся членами разных пространств, включаются в одно и то множество:// using-директива// формируется множество перегруженных функций из различных// пространств именusing namespace IBM;using namespace Disney;long double print(long double);int main() {print(1);print(3.1);return 0;// вызывается IBM::print(int)// вызывается Disney::print(double)}}Множество перегруженных функций с именем print в глобальной области видимостивключает функции print(int), print(double) и print(long double). Все онирассматриваются в main() при разрешении перегрузки, хотя первоначально былиопределены в разных пространствах имен.Итак, повторим, что перегруженные функции находятся в одной и той же областивидимости. В частности, они оказываются там в результате применения using-объявленийи using-директив, делающих доступными имена из других областей.С++ для начинающих4329.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() {calc( 34 );calc( si );// ...return 0;// вызывается C-функция calc( double )// вызывается функция C++ calc( const SmallInt & )соответствует типам переданных аргументов:}9.1.6. Указатели на перегруженные функции AМожно объявить указатель на одну из множества перегруженных функций. Например:С++ для начинающих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При использовании перегрузки складывается впечатление, что в программе можно иметьнесколько одноименных функций с разными списками параметров. Однако этолексическое удобство существует только на уровне исходного текста. В большинствесистем компиляции программы, обрабатывающие этот текст для получения исполняемого433С++ для начинающихкода, требуют, чтобы все имена были различны. Редакторы связей, как правило,разрешают внешние ссылки лексически. Если такой редактор встречает имя print дваили более раз, он не может различить их путем анализа типов (к этому моментуинформация о типах обычно уже потеряна). Поэтому он просто печатает сообщение оповторно определенном символе print и завершает работу.Чтобы разрешить эту проблему, имя функции вместе с ее списком параметровдекорируется так, чтобы получилось уникальное внутреннее имя.
Вызываемые послекомпилятора программы видят только это внутреннее имя. Как именно производитсятакое преобразование имен, зависит от реализации. Общая идея заключается в том, чтобыпредставить число и типы параметров в виде строки символов и дописать ее к именифункции.Как было сказано в разделе 8.2, такое кодирование гарантирует, в частности, что дваобъявления одноименных функций с разными списками параметров, находящиеся вразных файлах, не воспринимаются редактором связей как объявления одной и той жефункции.