13.11. Классы как члены пространства имен A

13.11. Классы как члены пространства имен A

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

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().