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

  • Как определить этот оператор в глобальной области видимости

  • ++j; // ошибка неоднозначности: global j или blip::j

  • Упражнение 18.19. Что, если бы вызов функции swap() был бы таким std::swap(v1.mem1, v2.mem1)

  • Язык программирования C Пятое издание


    Скачать 1.85 Mb.
    НазваниеЯзык программирования C Пятое издание
    Дата15.07.2019
    Размер1.85 Mb.
    Формат файлаpdf
    Имя файла620354-www.libfox.ru.pdf
    ТипДокументы
    #84130
    страница54 из 54
    1   ...   46   47   48   49   50   51   52   53   54
    Упражнение 18.13. Когда используются безымянные пространства имен?
    Упражнение 18.14. Предположим, имеется следующее объявление оператора operator*,
    являющегося членом вложенного пространства имен mathLib::MatrixLib: namespace mathLib { namespace MatrixLib { class matrix { /* ... */ }; matrix operator*
    (const matrix &, const matrix &);
    // ...
    }
    }

    Как определить этот оператор в глобальной области видимости?
    18.2.2. Использование членов пространства имен
    Обращение к члену пространства имен в формате имя_пространства_имен :: имя_члена является чересчур громоздким, особенно когда имя пространства имен слишком длинное. К счастью, существуют способы, которые облегчают использование имен членов пространства имен. Один из этих способов, объявление using (см. раздел 3.1), уже использовался в программах, приведенных выше. Другие способы, псевдонимы пространств
    Page 989/1103
    имен и директивы using будут описаны в этом разделе.Псевдонимы пространства имен
    Псевдоним пространства имен (namespace alias) применяется в качестве короткого синонима имени пространства имен. Например, длинное имя пространства имен может иметь следующий вид: namespace cplusplus_primer { /* ... */ };
    Ему может быть назначен более короткий синоним следующим образом: namespace primer = cplusplus_primer;
    Объявление псевдонима пространства имен начинается с ключевого слова namespace, за которым следует имя псевдонима пространства имен (короткое), сопровождаемое знаком =,
    первоначальное имя пространства имен и точка с запятой. Если имя первоначального пространства имен еще не было определено как пространство имен, произойдет ошибка.
    Псевдоним пространства имен может быть также применен к вложенному пространству имен:
    namespace Qlib = cplusplus_primer::QueryLib;
    Qlib::Query q;
    Пространство имен может иметь множество синонимов или псевдонимов. Все псевдонимы и первоначальное имя пространства имен равнозначны в применении. Объявления using
    (напоминание)
    Имена, представленные в объявлении using, подчиняются обычным правилам области видимости. Имя видимо от точки объявления using и до конца области видимости, в которой оно объявлено. Сущности внутренней области видимости скрывают одноименные сущности внешней. Короткие имена могут использоваться только в той области видимости, в которой они объявлены, а также в областях видимости, вложенных в нее. По завершении области видимости следует использовать полные имена.
    Объявление using может присутствовать в глобальной и локальной области видимости, а также в области видимости пространства имен или класса. Объявление using в области видимости класса ограничено именами, определенными в базовом классе определяемого класса (см. раздел 15.5). Директива using
    Подобно объявлению using, директива using (using directive) позволяет использовать не квалифицированную форму имен.
    Однако, в отличие от объявления using, здесь не сохраняется контроль над видимостью имен, поскольку все они видимы.
    Директива using начинается с ключевого слова using, за которым следует ключевое слово namespace, сопровождаемое именем пространства имен. Если имя пространства не было определено ранее, произойдет ошибка. Директива using может присутствовать в глобальной,
    локальной области видимости или в пространстве имен. Она не может присутствовать в области видимости класса.
    Предоставление директив using для таких пространств имен, как std, которые приложение не контролирует, возвращает все проблемы конфликта имени, присущие использованию нескольких библиотек. Директива using и область видимости
    Область видимости имен, указанных директивой using, гораздо сложнее, чем в случае
    Page 990/1103
    объявления using. Объявление using помещает имя непосредственно в ту же область видимости, в которой находится само объявление using. Объявление using подобно локальному псевдониму для члена пространства имен.
    Директива using не объявляет локальные псевдонимы для имен членов пространства имен.
    Вместо этого она поднимает члены пространства имен в ближайшую область видимости,
    которая содержит и пространство имен, и саму директиву using.
    Различие в области видимости между объявлением using и директивой using проистекает непосредственно из принципа действия этих средств. В случае объявления using само имя просто становится доступным в локальной области видимости. Директива using, напротив,
    делает доступным все содержимое пространства имен. Вообще, пространство имен способно включать определения, которые не могут присутствовать в локальной области видимости. Как следствие, директива using рассматривается как присутствующая в ближайшей области видимости окружающего пространства имен.
    Рассмотрим самый простой случай. Предположим, что в глобальной области видимости определено пространство имен А и функция f(). Если функция f() имеет директиву using для пространства имен А, функция f() будет вести себя так, как будто имена пространства имен А
    присутствовали в глобальной области видимости до определения функции f().
    // пространство имен А и функция f() определены в глобальной области
    // видимости namespace А { int i, j;
    } void f() { using namespace A; // переводит имена из области видимости А в
    // глобальную область видимости cout << i * j << endl; // использует i и j из пространства имен A
    // ...
    } Пример директив using
    Рассмотрим следующий пример: namespace blip { int i = 16, j = 15, k = 23; //
    Page 991/1103
    другие объявления
    } int j = 0; // ok: j в пространстве имен blip скрыта void manip() {
    // директива using; имена пространства имен blip "добавляются" к
    // глобальной области видимости using namespace blip; // конфликт между ::j и blip::j
    // обнаруживается только при использовании j
    ++i; // присваивает blip::i значение 17

    ++j; // ошибка неоднозначности: global j или blip::j?
    ++::j; // ok: присваивает глобальной j значение 1
    ++blip::j; // ok: присваивает blip::j значение 16 int k = 97; // локальная k скрывает blip::k
    ++k; // присваивает локальной k значение 98
    }
    Директива using в функции manip() делает все имена пространства имен blip доступными непосредственно. То есть функция manip() может обращаться к этим членам, используя краткую форму имен.
    Члены пространства имен blip выглядят так, как будто они были определены в одной области видимости. Если пространство имен blip определено в глобальной области видимости, его члены будут выглядеть так, как будто они объявлены в глобальной области видимости.
    Page 992/1103

    Когда пространство имен вводится в окружающую область видимости, имена в пространстве имен вполне могут вступить в конфликт с другими именами, определенными (включенными) в той же области видимости. Например, в функции manip() член j пространства имен blip вступает в конфликт с глобальным объектом j. Такие конфликты разрешимы, но для использования имени следует явно указать, какая версия имеется в виду. Любое использование имени j в пределах функции manip() ведет к неоднозначности.
    Чтобы использовать такое имя, как j, следует применить оператор области видимости,
    позволяющий указать требуемое имя. Для указания переменной j, определенной в глобальной области видимости, нужно написать ::j, а для определенной в пространстве имен blip — blip::j.
    Поскольку имена находятся в разных областях видимости, локальные объявления в пределах функции manip() могут скрыть некоторые из имен пространства имен. Локальная переменная k скрывает член пространства имен blip::k. Обращение к переменной k в пределах функции manip() вполне однозначно, это обращение к локальной переменной k. Заголовки и объявления using или директивы
    Заголовок, содержащий директиву или объявление using в своей области видимости верхнего уровня, вводит свои имена в каждый файл, который подключает заголовок. Обычно заголовки должны определять только те имена, которые являются частью его интерфейса, но не имена,
    используемые в его реализации. В результате файлы заголовка не должны содержать директив или объявлений using, кроме как в функциях или пространствах имен (см. раздел
    3.1). Внимание! Избегайте директив using
    Директивы using, вводящие в область видимости все имена из пространства имен, обманчиво просты в использовании. Единственный оператор делает видимыми имена всех членов пространства имен. Хоть этот подход может показаться простым, он создает немало проблем. Если в приложении использовано много библиотек и директива using сделает видимыми имена, определенные в них, то вновь возникнет проблема загромождения глобального пространства имен.
    Кроме того, не исключено, что при выходе новой версии библиотеки вполне работоспособная в прошлом программа перестанет компилироваться. Причиной этой проблемы может быть конфликт имен новой версии с именами, которые использовались прежде.
    Еще одна вызванная директивой using проблема неоднозначности обнаруживается только в момент применения. Столь позднее обнаружение означает, что конфликты могут возникать значительно позже применения определенной библиотеки. То есть при использовании в программе новой библиотеки могут возникнуть не обнаруженные ранее конфликты.
    Поэтому лучше не полагаться на директиву using и использовать объявление using для каждого конкретного имени пространства имен, используемого в программе. Это уменьшит количество имен, вводимых в пространство имен. Кроме того, ошибки неоднозначности,
    причиной которых является объявление using, обнаруживаются в точке объявления, а это существенно упрощает их поиск.
    Директивы using на самом деле полезны в файлах реализации самого пространства имен.
    Упражнения раздела 18.2.2
    Упражнение 18.15. Объясните различия между объявлением и директивой using.
    Упражнение 18.16. Объясните следующий код с учетом того, что объявления using для всех членов пространства имен Exercise находятся в области, помеченной как позиция 1 . Что, если вместо этого они располагаются в
    Page 993/1103
    позиции 2 ? Теперь ответьте на тот же вопрос, но замените объявления using директивой using для пространства имен Exercise. namespace Exercise { int ivar = 0; double dvar = 0; const int limit = 1000;
    } int ivar = 0;
    // позиция 1 void manip() {
    // позиция 2 double dvar = 3.1416; int iobj = limit + 1;
    ++ivar;
    ++::ivar;
    }
    Упражнение 18.17. Напишите код для проверки ответов на предыдущий вопрос.
    18.2.3. Классы, пространства имен и области видимости
    Поиск имен, используемых в пространстве имен, происходит согласно обычным правилам поиска в языке С++: сначала во внутренней, а затем во внешней области видимости. Имя,
    используемое в пространстве имен, может быть определено в одном из окружающих пространств имен, включая глобальное пространство имен. Однако учитываются только те имена, которые были объявлены перед точкой использования в блоках, которые все еще открыты. namespace A { int i; namespace В { int i; // скрывает A::i в В
    Page 994/1103
    int j; int f1() { int j; // j локальна для f1() и скрывает A::B::j return i; // возвращает B::i
    }
    } // пространство имен В закрыто, и его имена больше не видимы int f2() { return j; // ошибка: j не определена
    } int j = i; // инициализируется значением A::i
    }
    Когда класс расположен в пространстве имен, процесс поиска остается обычным: когда имя используется функцией-членом, его поиск начинается в самой функции, затем в пределах класса (включающий базовые классы), а потом в окружающих областях видимости, одной или несколькими из которых могли бы быть пространства имен: namespace A { int i; int k; class C1 { public:
    C1(): i(0), j(0) { } // ok: инициализирует C1::i и C1::j int f1() { return k; } // возвращает A::k int f2() { return h; } // ошибка: h не определена int f3();
    Page 995/1103
    private: int i; // скрывает A::i в C1 int j;
    }; int h = i; // инициализируется значением A::i
    }
    // член f3() определен вне класса C1 и вне пространства имен A int A::C1::f3() { return h; } // ok: возвращает A::h
    За исключением определений функций-членов, расположенных в теле класса (см. раздел
    7.4.1), области видимости всегда просматриваются снизу вверх: имя должно быть объявлено прежде его применения. Следовательно, оператор return функции f2() не будет откомпилирован. Он попытается обратиться к имени h из пространства имен А, но там оно еще не определено. Если бы это имя h было определено в пространстве имен А прежде определения класса C1, его использование было бы вполне допустимо. Аналогично использование имени h в функции f3() вполне допустимо, поскольку функция f3() определена уже после определения А::h.
    Порядок просмотра областей видимости при поиске имени определяется по полностью квалифицированному имени функции. Полностью квалифицированное имя указывает в обратном порядке области видимости, в которых происходит поиск.
    Спецификаторы A::C1::f3() указывают обратный порядок, в котором просматриваются области видимости класса и пространств имен. Первая область видимости — это функция f3(). Далее следует область видимости ее класса C1. Область видимости пространства имен А
    просматривается в последнюю очередь, перед переходом к области видимости, содержащей определение функции f3(). Зависимый от аргумента поиск и параметры типа класса
    Рассмотрим простую программу: std::string s; std::cin >> s;
    Как известно, этот вызов эквивалентен следующему (см. раздел 14.1): operator>>(std::cin, s);
    Функция operator>> определена библиотекой string, которая в свою очередь определяется в пространстве имен std. Но все же оператор >> можно вызвать без спецификатора std:: и без объявления using.
    Непосредственно обратиться к оператору вывода можно потому, что есть важное исключение
    Page 996/1103
    из правила сокрытия имен, определенных в пространстве имен. Когда объект класса передается функции, компилятор ищет пространство имен, в котором определяется класс аргумента в дополнение к обычному поиску области видимости. Это исключение применимо также к вызовам с передачей указателей или ссылок на тип класса.
    В этом примере, когда компилятор встречает "вызов" оператора operator>>, он ищет соответствующую функцию в текущей области видимости, включая области видимости,
    окружающие оператор вывода. Кроме того, поскольку выражение вывода имеет параметры типа класса, компилятор ищет также в пространствах имен, в которых определяются типы cin и s. Таким образом, для этого вызова компилятор просмотрит пространство имен std,
    определяющее типы istream и string. При поиске в пространстве имен std компилятор находит функцию вывода класса string.
    Это исключение из правил поиска позволяет функции, не являющейся членом класса, быть концептуально частью интерфейса к классу и использоваться без отдельного объявления using. Без этого исключения из правил поиска для оператора вывода всегда пришлось бы предоставлять соответствующее объявление using: using std::operator>>; // чтобы позволить cin >> s
    Либо пришлось бы использовать форму записи вызова функции, включающую спецификатор пространства имен: std::operator>>(std::cin, s); // ok: явное использование std::>>
    He было бы никакого способа использовать синтаксис оператора. Любое из этих объявлений выглядит неуклюже и существенно затруднило бы использование библиотеки ввода-вывода.
    Поиск и функции std::move() и std::forward()
    Многим, возможно, даже большинству программистов С++ никогда не понадобится зависимый от аргумента поиск. Обычно, если приложение определяет имя, уже определенное в библиотеке, истинно одно из двух: либо обычные правила перегрузки определят, относится ли данный конкретный вызов к библиотечной версии функции, или к версии приложения, или приложение никогда не сможет использовать библиотечную функцию.
    Теперь рассмотрите библиотечные функции move() и forward(). Обе являются шаблонами функций, и библиотека определяет их версии с одним параметром функции в виде ссылки на r-значение. Как уже упоминалось, параметру ссылки на r-значение в шаблоне функции может соответствовать любой тип (см. раздел 16.2.6). Если приложение определяет функцию по имени move(), получающую один параметр, то (вне зависимости от типа параметра) версия функции move() из приложения вступит в конфликт с библиотечной версией. Это справедливо и для функции forward().
    В результате конфликты имен для функций move() (и forward()) более вероятны, чем для других библиотечных функций. Кроме того, поскольку функции move() и forward()
    осуществляют весьма специфические для типа манипуляции, вероятность того, что в приложении специально необходимо переопределить поведение этих функций, довольно мала.
    Page 997/1103

    Тот факт, что конфликты имен с этими функциями более вероятны (и менее вероятно, что намеренными), объясняет, почему их имена всегда следует использовать полностью квалифицированными (см. раздел 12.1.5). Форма записи std::move(), а не просто move()
    гарантирует применение версии из стандартной библиотеки. Дружественные объявления и зависимый от аргумента поиск
    Напомним, что на момент, когда класс объявляет функцию дружественной (см. раздел 7.2.1),
    объявление функции необязательно должно быть видимым. Если объявление функции еще не видимо, результатом объявления ее дружественной окажется помещение объявления данной функции или класса в окружающую область видимости. Комбинация этого правила и зависимого от аргумента поиска может привести к неожиданным результатам: namespace A { class С {
    // два друга; ничего не объявлено кроме дружественных отношений
    // эти функции неявно являются членами пространства имен A friend void f2(); // не будет найдено, если не объявлено иное friend void f(const C&); // найдено зависимым от аргумента
    // поиском
    };
    }
    Здесь функции f() и f2() являются членами пространства имен А. Зависимый от аргумента поиск позволяет вызвать функцию f(), даже если для нее нет никакого дополнительного объявления: int main() {
    A::C cobj; f(cobj); // ok: находит A::f() по объявлению дружественным в A::C f2(); // ошибка: A::f2() не объявлена
    }
    Поскольку функция f() получает аргумент типа класса и неявно объявляется в том же
    Page 998/1103
    пространстве имен, что и C, при вызове она будет найдена. Так как у функции f2() никакого параметра нет, она не будет найдена. Упражнения раздела 18.2.3
    Упражнение 18.18. С учетом следующего типичного определения функции swap() в разделе
    13.3 определите, какая ее версия используется, если mem1 имеет тип string. Что, если mem1
    имеет тип int? Объясните, как будет проходить поиск имен в обоих случаях. void swap(T v1, T v2) { using std::swap; swap(v1.mem1, v2.mem1);
    // обмен остальных членов типа Т
    }

    Упражнение 18.19. Что, если бы вызов функции swap() был бы таким std::swap(v1.mem1, v2.mem1)?
    18.2.4. Перегрузка и пространства имен
    Пространства имен могут повлиять на подбор функции (см. раздел 6.4) двумя способами.
    Один из них вполне очевиден: объявление или директива using может добавить функцию в набор кандидатов. Второй способ менее очевиден. Зависимый от аргумента поиск и перегрузка
    Как упоминалось в предыдущем разделе, поиск имен функций, имеющих один или несколько аргументов типа класса, осуществляется также и в пространстве имен, в котором определен класс каждого аргумента. Это правило влияет также и на выбор кандидатов. Каждое пространство имен, в котором определен класс, используемый в качестве типа параметра (а также те, в которых определены его базовые классы), участвует в поиске функции-кандидата.
    Все функции этих пространств имен, которые имеют имя, совпадающее с использованным при вызове, будут добавлены в набор кандидатов. Эти функции будут добавлены даже тогда,
    когда они не видимы в точке обращения : namespace NS { class Quote { /* ... */ }; void display(const Quote&) { /* ... */ }
    }
    //
    Базовый класс Bulk_item объявлен в пространстве имен NS class Bulk_item : public NS::Quote { /* ... */ };
    Page 999/1103
    int main() {
    Bulk_item book1; display(book1); return 0;
    }
    Аргумент book1 функции display() имеет тип класса Bulk_item. Функциями-кандидатами для этого вызова функции display() будут не только функции с объявлениями, видимыми на момент вызова, но и те, которые объявлены в пространстве имен класса Bulk_item и его базового класса Quote. Таким образом, функция display(const Quote&), объявленная в пространстве имен NS, будет добавлена в набор функций кандидатов. Перегрузка и объявления using
    Чтобы уяснить взаимодействие объявлений using и перегрузки, важно помнить, что объявление using объявляет только имя, а не конкретную функцию (см. раздел 15.6): using NS::print(int); // ошибка: нельзя указать список параметров using NS::print; // ok: в объявлении using указывают только имена
    Когда объявление using используется для функции, все версии этой функции переводятся в текущую область видимости.
    Объявление using подключает все версии перегруженной функции, чтобы не нарушить интерфейс пространства имен. Ведь предоставляя разные версии функции, автор библиотеки имел на то весомую причину. Разрешив пользователям игнорировать некоторые
    (но не все) функции из набора перегруженных версий, можно получить довольно странное поведение программы.
    Функции, предоставленные объявлением using, перегружают любые другие объявления одноименных функций, уже находящихся в данной области видимости.
    Если объявление using расположено в локальной области видимости, эти имена скрывают существующие объявления для того имени во внешней области видимости. Если объявление using вводит функцию в область видимости, в которой уже есть функция с тем же именем и тем же списком параметров, объявление using окажется ошибочным. В противном случае объявление using создаст дополнительный перегруженный экземпляр данной функции. В
    результате набор функций-кандидатов увеличится. Перегрузка и директивы using
    Директива using переводит члены пространства имен в окружающую область видимости.
    Если имя функции пространства имен совпадает с именем функции той области видимости, в которую помещено пространство имен, эта функция будет добавлена в набор перегруженных функций. namespace libs_R_us { extern void print(int); extern void print(double);
    Page 1000/1103
    1   ...   46   47   48   49   50   51   52   53   54


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