Классы как члены пространства имен *
Представленные до сих пор классы определены в области видимости глобального пространства имен. Но их можно определять и в объявленных пользователем пространствах. Имя класса, определенного таким образом, доступно только в области видимости этого пространства, т.е. оно не конфликтует с именами, объявленными в других пространствах имен. Например:
namespace cplusplus_primer {
class Node { /* ... */ };
}
namespace DisneyFeatureAnimation {
class Node { /* ... */ };
}
Node *pnode; // ошибка: Node не видно в глобальной области видимости
// правильно: объявляет nodeObj как объект
// квалифицированного типа DisneyFeatureAnimation::Node
DisneyFeatureAnimation::Node nodeObj;
// using-объявление делает Node видимым в глобальной области видимости
using cplusplus_primer::Node;
Node another; // cplusplus_primer::Node
Как было показано в двух предыдущих разделах, член класса (функция-член, статический член или вложенный класс) может быть определен вне его тела. Если мы реализуем библиотеку и помещаем определения наших классов в объявленное пользователем пространство имен, то где расположить определения членов, находящиеся вне тел своих классов? Их можно разместить либо в пространстве имен, которое содержит определение самого внешнего класса, либо в одном из объемлющих его пространств. Это дает возможность организовать код библиотеки следующим образом:
// --- primer.h ---
namespace cplusplus_primer {
class List {
// ...
private:
class ListItem {
public:
void check_status();
int action();
// ...
};
};
}
// --- primer.C ---
#include "primer.h"
namespace cplusplus_primer {
// правильно: check_status() определено в том же пространстве имен,
// что и List
void List::ListItem::check_status() { }
}
// правильно: action() определена в глобальной области видимости
// в пространстве имен, объемлющем определение класса List
// Имя члена квалифицировано именем пространства
int cplusplus_primer::List::ListItem::action() { }
Члены вложенного класса ListItem можно определить в пространстве имен cplusplus_primer, которое содержит определение List, или в глобальном пространстве, включающем определение cplusplus_primer. В любом случае имя члена в определении должно быть квалифицировано именами объемлющих классов и объявленных пользователем пространств, вне которых находится объявление члена.
Как происходит разрешение имени в определении члена, которое находится в объявленном пользователем пространстве? Например, как будет разрешено someVal:
int cplusplus_primer::List::ListItem::action() {
int local = someVal;
// ...
}
Сначала просматриваются локальные области видимости в определении функции-члена, затем поиск продолжается в области видимости ListItem, затем – в области видимости List. До этого момента все происходит так же, как в процессе разрешения имен, описанном в разделе 13.10. Далее просматриваются объявления из пространства cplusplus_primer и наконец объявления в глобальной области видимости, причем во внимание принимаются только те, которые расположены до определения функции-члена action():
// --- primer.h ---
namespace cplusplus_primer {
class List {
// ...
private:
class ListItem {
public:
int action();
// ...
};
};
const int someVal = 365;
}
// --- primer.C ---
#include "primer.h"
namespace cplusplus_primer {
int List::ListItem::action() {
// правильно: cplusplus_primer::someVal
int local = someVal;
// ошибка: calc() еще не объявлена
double result = calc( local );
// ...
}
double calc(int) { }
// ...
}
Определение пространства имен cplusplus_primer не является непрерывным. Определения класса List и объекта someVal размещены в первом его разделе, который находится в заголовочном файле primer.h. Определение функции calc() появляется в определении пространства имен, расположенном в файле реализации primer.C. Использование calc() внутри action() ошибочно, так как она объявлена после использования. Если calc() – часть интерфейса cplusplus_primer, ее следовало бы объявить в той части данного пространства, которая находится в заголовочном файле:
// --- primer.h ---
namespace cplusplus_primer {
class List {
// ...
}
const int someVal = 365;
double calc(int);
}
Если же calc() используется только в action() и не является частью интерфейса пространства имен, то ее нужно объявить перед action(), чтобы можно было ссылаться на нее внутри определения action().
Здесь прослеживается аналогия с процессом поиска объявлений в глобальной области видимости, о котором мы говорили в предыдущих разделах: объявления, предшествующие определению члена, принимаются во внимание, тогда как следующие за ним игнорируются.
Довольно просто запомнить, в каком порядке просматриваются области видимости при поиске имени из определения функции, расположенного вне определения класса. Имена, которыми квалифицировано имя члена, указывают порядок рассмотрения пространств. Например, имя action() в предыдущем примере квалифицируется так:
cplusplus_primer::List::ListItem::action()
Квалификаторы cplusplus_primer::List::ListItem:: записаны в порядке, обратном тому, в котором просматриваются имена областей видимости классов и пространств имен. Сначала поиск ведется в области ListItem, затем продолжается в объемлющем классе List и наконец в пространстве cplusplus_primer, предшествующем той области, в которой находится определение action(). Во время поиска в любой области видимости класса просматриваются все объявления членов, а в любом пространстве имен – только те объявления, которые встречались перед определением члена.
Класс, определенный в области видимости пространства имен, потенциально виден во всей программе. Если заголовочный файл primer.h включен в несколько исходных файлов, то имя cplusplus_primer::List везде относится к одному и тому же классу. Класс – это сущность, для которой в программе может быть более одного определения. Определение класса должно присутствовать один раз в каждом исходном файле, где определяются или используются сам класс или его члены. Однако оно должно быть одинаковым во всех файлах, где встречается, поэтому его следует помещать в заголовочный файл, например primer.h. Затем такой файл можно включать в любой исходный, где определяются или используются члены класса. Это предотвратит несоответствия в случае, когда определение класса записывается более одного раза.
Невстроенные функции-члены и статические данные-члены класса в пространстве имен – это также программные сущности. Однако они могут быть определены лишь один раз во всей программе. Поэтому их определения помещаются не в заголовочный, а в отдельный исходный файл типа primer.C.
Упражнение 13.22
Используя класс iStack, определенный в упражнении 13.21, объявите классы исключений pushOnFull и popOnEmpty как члены пространства имен LibException:
namespace LibException {
class pushOnFull{ };
class popOnEmpty{ };
}
а сам iStack – членом пространства имен Container. Модифицируйте соответствующим образом определение данного класса и его функций-членов, а также определение main().