НА ГЛАВНУЮ
Меню сайта
Категория
Ghost++ [1]
С++ [55]
Развлечение
ON - LINE
Опрос
На какие on-line игры вы тратите больше времени?
Всего ответов: 247
Оbserver Ward

Онлайн всего: 1
Гостей: 1
Пользователей: 0


Друзья сайта
Заведи себе Бота
Hаша кнопка
Для обмена банерами , наша кнопка для размещения у вас на сайте

Клансайт USSR


Главная » Статьи » Программирование » С++

8. Область видимости и время жизни (3)
8.5.5. ПОО и члены пространства имен

Как уже было сказано, определение пространства имен может состоять из разрозненных частей и размещаться в разных файлах. Следовательно, член пространства разрешено объявлять во многих файлах. Например:

// primer.h
namespace cplusplus_primer {
// ...
   void inverse( matrix & );
}

// usel.C
#include "primer.h"
// объявление cplusplus_primer::inverse() в use1.C

// use2.C
#include "primer.h"
// объявление cplusplus_primer::inverse() в use2.C

Объявление cplusplus::inverse() в primer.h ссылается на одну и ту же функцию в обоих исходных файлах use1.C и use2.C.
Член пространства имен является глобальной сущностью, хотя его имя квалифицировано. Требование ПОО (правило одного определения, см. раздел 8.2) распространяется и на него. Чтобы удовлетворить этому требованию, программы, в которых используются пространства имен, обычно организуют следующим образом:

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

      // ---- primer.h ----
      namespace cplusplus_primer {
         class matrix { /* ... */ };
         // объявления функций
         extern matrix operator+ ( const matrix &m1, const matrix &m2 );
         extern void inverse( matrix & );
           
         // объявления объектов
         extern bool error_state;
      }

   2. Определения этих членов помещают в исходный файл, содержащий реализацию:

      // ---- primer.C ---- #include "primer.h"
         namespace cplusplus_primer {
         // определения функций
         void inverse( matrix & )
         { /* ... */ }
         matrix operator+ ( const matrix &ml, const matrix &m2 )
         { /" ... */ }
         // определения объектов
         bool error_state = false;
      }

Для объявления объекта без его определения используется ключевое слово extern, как и в случае такого объявления в глобальной области видимости.
8.5.6. Безымянные пространства имен

Может возникнуть необходимость определить объект, функцию, класс или любую другую сущность так, чтобы она была видимой только в небольшом участке программы. Это еще один способ решения проблемы засорения глобального пространства имен. Поскольку мы уверены, что эта сущность используется ограниченно, можно не тратить время на выдумывание уникального имени. Если мы объявляем объект внутри функции или блока, его имя видимо только в этом блоке. А как сделать некоторую сущность доступной нескольким функциям, но не всей программе?
Предположим, мы хотим реализовать набор функций для сортировки вектора типа double:

// ----- SortLib.h -----
void quickSort( double *, double * );
void bubbleSort( double *, double * );
void mergeSort( double *, double * );
void heapSort( double *, double * );

Все они используют одну и ту же функцию swap() для того, чтобы менять местами элементы вектора. Однако она не должна быть видна во всей программе, поскольку нужна только четырем названным функциям. Локализуем ее в файле SortLib.C. Приведенный код не дает желаемого результата. Как вы думаете, почему?

// ----- SortLib.C -----
void swap( double *dl, double *d2 ) { /* ... */ }

// только эти функции используют swap()
void quickSort( double *d1, double *d2 ) { /* ... */ }
void bubbleSort( double *d1, double *d2 ) { /* ... */ }
void mergeSort( double *d1, double *d2 ) { /* ... */ }
void heapSort( double *d1, double *d2 ) { /* ... */ }

Хотя функция swap() определена в файле SortLib.C и не появляется в заголовочном файле SortLib.h, где содержится описание интерфейса библиотеки сортировки, она объявлена в глобальной области видимости. Следовательно, это имя является глобальным, при этом сохраняется возможность конфликта с другими именами.
Язык С++ предоставляет возможность использования безымянного пространства имен для объявления сущности, локальной по отношению к файлу. Определение такого пространства начинается ключевым словом namespace. Очевидно, что никакого имени за этим словом нет, а сразу же идет блок в фигурных скобках, содержащий различные объявления. Например:

// ----- SortLib.C -----
namespace {
   void swap( double *dl, double *d2 ) { /* ... */ }
}
// определения функций сортировки не изменяются

Функция swap() видна только в файле SortLib.C. Если в другом файле в безымянном пространстве имен содержится определение swap(), то это другая функция. Наличие двух функций swap() не является ошибкой, поскольку они различны. Безымянные пространства имен отличаются от прочих: определение такого пространства локально для одного файла и не может размещаться в нескольких.
Имя swap() может употребляться в неквалифицированной форме в файле SortLib.C после определения безымянного пространства. Оператор разрешения области видимости для ссылки на его члены не нужен.

void quickSort( double *d1, double *d2 ) {
   // ...
   double* elem = d1;
   // ...
   // ссылка на член безымянного пространства имен swap()
   swap( d1, elem );
   // ...
}

Члены безымянного пространства имен относятся к сущностям программы. Поэтому функция swap() может быть вызвана во время выполнения. Однако имена этих членов видны только внутри одного файла.
До того как в стандарте С++ появилось понятие пространства имен, наиболее удачным решением проблемы локализации было использование ключевого слова static, унаследованного из С. Член безымянного пространства имеет свойства, аналогичные глобальной сущности, объявленной как static. В языке С такая сущность невидима вне файла, в котором объявлена. Например, текст из SortLib.C можно переписать на С, сохранив свойства swap():

// SortLib.C
// swap() невидима для других файлов программы
static void swap( double *d1, double *d2 ) { /* ... */ }
// определения функций сортировки такие же, как и раньше

Во многих программах на С++ используются объявления с ключевым словом static. Предполагается, что они должны быть заменены безымянными пространствами имен по мере того, как все большее число компиляторов начнет поддерживать это понятие.
Упражнение 8.11

Зачем нужно определять собственное пространство имен в программе?
Упражнение 8.12

Имеется следующее объявление operator*(), члена вложенного пространства имен cplusplus_primer::MatrixLib:

namespace cplusplus_primer {
   namespace MatrixLib {
      class matrix { /*...*/ };
      matrix operator* ( const matrix &, const matrix & );
      // ...
   }
}

Как определить эту функцию в глобальной области видимости? Напишите только прототип.
Упражнение 8.13

Объясните, зачем нужны безымянные пространства имен.
8.6. Использование членов пространства имен А

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

Псевдоним пространства имен используется для задания короткого синонима имени пространства. Например, длинное имя
namespace International_Business_Machines
{ /* ... */ }
может быть ассоциировано с более коротким синонимом:

namespace IBM = International_Business_Machines;

Объявление псевдонима начинается ключевым словом namespace, за которым следует короткий псевдоним, а за ним – знак равенства и исходное полное имя пространства. Если полное имя не соответствует никакому известному пространству, это ошибка.
Псевдоним может относиться и к вложенному пространству имен. Вспомним слишком длинное определение функции func() выше:

#include "primer.h"
// трудно читать!
void func( cplusplus_primer::MatrixLib::matrix &m )
{
  // ...
  cplusplLis_primer::MatrixLib::inverse( m );
  return m;
}

Разрешается задать псевдоним для обозначения вложенного cplusplLis_primer::MatrixLib, сделав определение функции более удобным для восприятия:

#include "primer.h"
// более короткий псевдоним
namespace mlib = cplusplus_primer::MatrixLib;

// читать проще!
void func( mlib::matrix &m )
{
  // ...
  mlib::inverse( m );
  return m;
}

Одно пространство имен может иметь несколько взаимозаменяемых псевдонимов. Например, если псевдоним Lib ссылается на cplusplus_primer, то определение функции func() может выглядеть и так:

// псевдоним alias относится к пространству имен cplusplus_primer
namespace alias = Lib;

void func( cplusplus_primer::matrix &m ) {
  // ...
  alias::inverse( m );
  return m;
}

8.6.2. Using-объявления

Имеется механизм, позволяющий обращаться к членам пространства имен, используя их имена без квалификатора, т.е. без префикса namespace_name::. Для этого применяются using-объявления.
Using-объявление начинается ключевым словом using, за которым следует квалифицированное имя члена пространства. Например:

namespace cplusplus_primer {
   namespace MatrixLib {
      class matrix { /* ... */ };
      // ...
   }
}

// using-объявление для члена matrix
using cplusplus_primer::MatrixLib::matrix;

Using-объявление вводит имя в ту область видимости, в которой оно использовано. Так, предыдущее using-объявление делает имя matrix глобально видимым.
После того как это объявление встретилось в программе, использование имени matrix в глобальной области видимости или во вложенных в нее областях относится к этому члену пространства имен. Пусть далее идет следующее объявление:

void func( matrix &m );

Оно вводит функцию func() с параметром типа cplusplus_primer:: MatrixLib::matrix.
Using-объявление ведет себя подобно любому другому объявлению: оно имеет область видимости, и имя, введенное им, можно употреблять начиная с места объявления и до конца области видимости. Using-объявление может использоваться в глобальной области видимости, равно как и в области видимости любого пространства имен. Оно употребляется и в локальной области. Имя, вводимое using-объявлением, как и любым другим, имеет следующие характеристики:

    * оно должно быть уникальным в своей области видимости;
    * оно скрывает одноименную сущность во внешней области;
    * оно скрывается объявлением одноименной сущности во вложенной области.

Например:

namespace blip {
    int bi = 16, bj = 15, bk = 23;
    // прочие объявления
}
int bj = 0;
void manip() {
    using blip::bi; // bi в функции manip() ссылается на blip::bi
    ++bi;           // blip::bi == 17
    using blip::bj; // скрывает глобальную bj
                    // bj в функции manip()ссылается на blip::bj
    ++bj;           // blip::bj == 16
    int bk;         // объявление локальной bk
    using blip::bk; // ошибка: повторное определение bk в manip()
}
int wrongInit = bk; // ошибка: bk невидима
                    // надо использовать blip::bk

Using-объявления в функции manip() позволяют ссылаться на членов пространства blib с помощью неквалифицированных имен. Такие объявления не видны вне manip(), и неквалифицированные имена могут применяться только внутри этой функции. Вне ее необходимо употреблять квалифицированные имена.
Using-объявление упрощает использование членов пространства имен. Оно вводит только одно имя. Using-объявление может находиться в определенной области видимости, и, значит, мы способны точно указать, в каком месте программы те или иные члены разрешается употреблять без дополнительной квалификации.
В следующем подразделе мы расскажем, как ввести в определенную область видимости все члены некоторого пространства имен.
8.6.3. Using-директивы

Пространства имен появились в стандартном С++. Предыдущие версии С++ их не поддерживали, и, следовательно, поставляемые библиотеки не помещали глобальные объявления в пространства имен. Множество программ на С++ было написано еще до того, как компиляторы стали поддерживать такую опцию. Заключая содержимое библиотеки в пространство имен, мы можем испортить старое приложение, использующее ее предыдущие версии: все имена из этой библиотеки становятся квалифицированными, т.е. должны включать имя пространства вместе с оператором разрешения области видимости. Те приложения, в которых эти имена употребляются в неквалифицированной форме, перестают компилироваться.
Сделать видимыми имена из библиотеки, используемой в нашей программе, можно с помощью using-объявления. Предположим, что файл primer.h содержит интерфейс новой версии библиотеки, в котором глобальные объявления помещены в пространство имен cplusplus_primer. Нужно заставить нашу программу работать с новой библиотекой. Два using-объявления сделают видимыми имена класса matrix и функции inverse() из пространства cplusplus_primer:

#include "primer.h"
using cplusplus_primer::matrix;
using cplusplus_primer::inverse;

// using-объявления позволяют использовать
// имена matrix и inverse без спецификации
void func( matrix &m ) {
  // ...
  inverse( m );
  return m;
}

Но если библиотека достаточно велика и приложение часто использует имена из нее, то для подгонки имеющегося кода к новой библиотеке может потребоваться много using-объявлений. Добавлять их все только для того, чтобы старый код скомпилировался и заработал, утомительно и чревато ошибками. Решить эту проблему помогают using-директивы, облегчающие переход на новую версию библиотеки, где впервые стали применяться пространства имен.
Using-директива начинается ключевым словом using, за которым следует ключевое слово namespace, а затем имя некоторого пространства имен. Это имя должно ссылаться на определенное ранее пространство, иначе компилятор выдаст ошибку. Using-директива позволяет сделать все имена из этого пространства видимыми в неквалифицированной форме.
Например, предыдущий фрагмент кода может быть переписан так:

#include "pnmer.h"

// using-директива: все члены cplusplus_primer
// становятся видимыми
using namespace cplusplus_primer;

// имена matrix и inverse можно использовать без спецификации
void func( matrix &m ) {
  // ...
  inverse( m );
  return m;
}

Using-директива делает имена членов пространства имен видимыми за его пределами, в том месте, где она использована. Например, приведенная using-директива создает иллюзию того, что все члены cplusplus_primer объявлены в глобальной области видимости перед определением func(). При этом члены пространства имен не получают локальных псевдонимов, а как бы перемещаются в новую область видимости. Код

namespace A {
   int i, j;
}

выглядит как

int i, J;

для фрагмента программы, содержащего в области видимости следующую using-директиву:
using namespace A;
Рассмотрим пример, позволяющий подчеркнуть разницу между using-объявлением (которое сохраняет пространство имен, но создает ассоциированные с его членами локальные синонимы) и using-директивой (которая полностью удаляет границы пространства имен).

namespace blip {
    int bi = 16, bj = 15, bk = 23;
    // прочие объявления
}
int bj = 0;
void manip() {
    using namespace blip; // using-директива -
                          // коллизия имен ::bj and blip::bj
                          // обнаруживается только при
                          // использовании bj
    ++bi;                 // blip::bi == 17
    ++bj;                 // ошибка: неоднозначность
                          // глобальная bj или blip::bj?
    ++::bj;               // правильно: глобальная bj == 1
    ++blip::bj;           // правильно: blip::bj == 16
    int bk = 97;             // локальная bk скрывает blip::bk
    ++bk;                   // локальная bk == 98
}

Во-первых, using-директивы имеют область видимости. Такая директива в функции manip() относится только к блоку этой функции. Для manip() члены пространства имен blip выглядят так, как будто они объявлены в глобальной области видимости, а следовательно, можно использовать их неквалифицированные имена. Вне этой функции необходимо употреблять квалифицированные.
Во-вторых, ошибки неоднозначности, вызванные применением using-директивы, обнаруживают себя при реальном обращении к такому имени, а не при встрече в тексте самой этой директивы. Например, переменная bj, член пространства blib, выглядит для manip() как объявленная в глобальной области видимости, вне blip. Однако в глобальной области уже есть такая переменная. Возникает неоднозначность имени bj в функции manip(): оно относится и к глобальной переменной, и к члену пространства blip. Ошибка проявляется только при упоминании bj в функции manip(). Если бы это имя вообще не использовалось в manip(), коллизия не проявилась бы.
В-третьих, using-директива не затрагивает употребление квалифицированных имен. Когда в manip() упоминается ::bj, имеется в виду переменная из глобальной области видимости, а blip::bj обозначает переменную из пространства имен blip.
И наконец члены пространства blip выглядят для функции manip() так, как будто они объявлены в глобальной области видимости. Это означает, что локальные объявления внутри manip() могут скрывать имена членов пространства blip. Локальная переменная bk скрывает blip::bk. Ссылка на bk внутри manip() не является неоднозначной – речь идет о локальной переменной.
Using-директивы использовать очень просто: стоит написать одну такую директиву, и все члены пространства имен сразу становятся видимыми. Однако чрезмерное увлечение ими возвращает нас к старой проблеме засорения глобального пространства имен:

namespace cplusplus_primer {
   class matrix { };
   // прочие вещи ...
}
namespace DisneyFeatureAnimation {
   class matrix { };
   // здесь тоже ...

   using namespace cplusplus_primer;
   using namespace DisneyFeatureAnimation;

   matrix m; //ошибка, неоднозначность:
   // cplusplus_primer::matrix или DisneyFeatureAnimation::matrix?

Ошибки неоднозначности, вызываемые using-директивой, обнаруживаются только в момент использования. В данном случае – при употреблении имени matrix. Такая ошибка, найденная не сразу, может стать сюрпризом: заголовочные файлы не менялись и никаких новых объявлений в программу добавлено не было. Ошибка появилась после того, как мы решили воспользоваться новыми средствами из библиотеки.
Using-директивы очень полезны при переводе приложений на новые версии библиотек, использующие пространства имен. Однако употребление большого числа using-директив возвращает нас к проблеме засорения глобального пространства имен. Эту проблему можно свести к минимуму, если заменить using-директивы более селективными using-объявлениями. Ошибки неоднозначности, вызываемые ими, обнаруживаются в момент объявления. Мы рекомендуем пользоваться using-объявлениями, а не using-директивами, чтобы избежать засорения глобального пространства имен в своей программе.
8.6.4. Стандартное пространство имен std

Все компоненты стандартной библиотеки С++ находятся в пространстве имен std. Каждая функция, объект и шаблон класса, объявленные в стандартном заголовочном файле, таком, как <vector> или <iostream>, принадлежат к этому пространству.
Если все компоненты библиотеки объявлены в std, то какая ошибка допущена в данном примере:

#include <vector>
#include <string>
#include <iterator>

int main()
{
   // привязка istream_iterator к стандартному вводу
   istream_iterator<string> infile( cin );

   // istream_iterator, отмечающий end-of-stream
   istream_iterator<string> eos;

   // инициализация svec элементами, считываемыми из cin
   vector<string> svec( infile, eos );
   // ...
}

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

    * заменить имена членов пространства std в этом примере соответствующими специфицированными именами;
    * применить using-объявления, чтобы сделать видимыми используемые члены пространства std;
    * употребить using-директиву, сделав видимыми все члены пространства std.

Членами пространства имен std в этом примере являются: шаблон класса istream_iterator, стандартный входной поток cin, класс string и шаблон класса vector.
Простейшее решение – добавить using-директиву после директивы препроцессора #include:
using namespace std;
В данном примере using-директива делает все члены пространства std видимыми. Однако не все они нам нужны. Предпочтительнее пользоваться using-объявлениями, чтобы уменьшить вероятность коллизии имен при последующем добавлении в программу глобальных объявлений.
Using-объявления, необходимые для компиляции этого примера, таковы:

using std::istream_iterator;
using std::string;
using std::cin;
using std::vector;

Но куда их поместить? Если программа состоит из большого количества файлов, можно для удобства создать заголовочный файл, содержащий все эти using-объявления, и включать его в исходные файлы вслед за заголовочными файлами стандартной библиотеки.
В нашей книге мы не употребляли using-объявлений. Это сделано, во-первых, для того, чтобы сократить размер кода, а во-вторых, потому, что большинство примеров компилировались в реализации С++, не поддерживающей пространства имен. Подразумевается, что using-объявления указаны для всех членов пространства имен std, используемых в примерах.
Упражнение 8.14

Поясните разницу между using-объявлениями и using-директивами.
Упражнение 8.15

Напишите все необходимые using-объявления для примера из раздела 6.14.
Упражнение 8.16

Возьмем следующий фрагмент кода:

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;
}

Каковы будут значения объявлений и выражений, если поместить using-объявления для всех членов пространства имен Exercise в точку //1? В точку //2? А если вместо using-объявлений использовать using-директиву?
Категория: С++ | Добавил: r2d2 (29.09.2011)
Просмотров: 886 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Born in Ussr
Залогиниться
Турниры

/j clan ussr /j clan cccp