Чтение онлайн

ЖАНРЫ

Стандарты программирования на С++. 101 правило и рекомендация

Александреску Андрей

Шрифт:

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

В этом разделе мы считаем наиболее значимой рекомендацию 58 — "Храните типы и функции в разных пространствах имен, если только они не предназначены для совместной работы".

57. Храните типы и их свободный интерфейс в одном пространстве имен

Резюме

Функции, не являющиеся членами и разработанные как часть интерфейса класса

X
(в особенности операторы и вспомогательные
функции), должны быть определены в том же пространстве имен, что и
X
, что обеспечивает их корректный вызов.

Обсуждение

Открытый интерфейс класса образуют не только открытые функции-члены, но и функции, не являющиеся членами. Принцип Интерфейса гласит: для класса

X
все функции (включая функции, не являющиеся членами), которые "упоминают
X
" и "поставляются вместе с
X
" в одном и том же пространстве имен, являются логической частью
X
, поскольку образуют часть интерфейса
X
(см. рекомендацию 44 и [Sutter00]).

Язык С++ спроектирован с явным учетом Принципа Интерфейса. Причина, по которой в язык добавлен поиск, зависящий от аргумента (argument-dependent lookup — ADL), известный также как поиск Кёнига, заключается в том, чтобы обеспечить коду, использующему объект

x
типа
X
, возможность работать с частью его интерфейса, состоящей из функций, не являющихся членами (например, инструкция
cout << x
использует оператор
operator<<
, который не является членом класса
X
) так же легко, как и функции-члены (например, вызов
x.f
) не требует выполнения специального поиска, поскольку очевидно, что поиск
f
выполняется в области видимости
X
). ADL обеспечивает для свободных функций, которые получают объект
X
в качестве аргумента и поставляются вместе с определением
X
ту же простоту использования, что и для функций-членов интерфейса
X
. Одним из главных мотивов принятия ADL был, в частности, класс
std::string
(см. [Sutter00]).

Рассмотрим класс

X
, определенный в пространстве имен N:

class X {

publiс:

 void f;

};

X operator+(const X&, const X&);

В вызывающей функции обычно пишется код наподобие

x3=x1+x2
, где
x1
,
x2
и
x3
— объекты типа
X
. Если оператор
operator+
объявлен в том же пространстве имен, что и
X
, никаких проблем не возникает, и такой код отлично работает, поскольку оператор
operator+
будет легко найден с помощью ADL.

Если же оператор

operator+
не объявлен в том же пространстве имен, что и
X
, вызывающий код работать не будет. В этом случае имеется два способа заставить его заработать. Первый состоит в использовании явно квалифицированного оператора

x3 = N::operator+(x1, x2);

Грустная картина — невозможность использовать естественный синтаксис оператора, который, собственно, и был главной целью введения перегрузки операторов в язык программирования. Другой способ заставить работать приведенный ранее код — использовать инструкцию

using
:

using N::operator+;

// или: using namespace N;

x3 = x1 + x2;

Применение

using
— совершенно нормальная и приемлемая вещь (см. рекомендацию 59), но все проблемы решаются гораздо проще, если автор
X
изначально поступает корректно и помещает оператор
operator+
, работающий с объектами
X
, в то же пространство имен, где находится
X
.

"Оборотная сторона" этого вопроса рассматривается в рекомендации 58.

Примеры

Пример 1. Операторы. Операторы работы с потоками

operator<<
и
operator>>
для объектов некоторого класса
X
, вероятно, относятся к наиболее ярким примерам функций, которые вполне очевидно являются частью интерфейса класса
X
, но при этом всегда представляют собой свободные функции (это обязательное условие, поскольку левый аргумент этих операторов — поток, а не объект
X
). Та же аргументация применима и к другим операторам, не являющимся членами
X
. Убедитесь, что ваши операторы находятся в том же пространстве имен, что и класс, с которым они работают. Если у вас есть возможность выбора, лучше делать операторы и все прочие функции не членами и не друзьями класса (см. рекомендацию 44).

Пример 2. Прочие функции. Если автор

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

Ссылки

[Stroustrup00] §8.2, §10.3.2, §11.2.4 • [Sutter00] §31-34

58. Храните типы и функции в разных пространствах имен, если только они не предназначены для совместной работы

Резюме

Оберегайте ваши типы от непреднамеренного поиска, зависящего от аргументов (argument-dependent lookup — ADL, известный также как поиск Кёнига); однако преднамеренный поиск должен завершаться успешно. Этого можно добиться путем размещения типов в своих собственных пространствах имен (вместе с непосредственно связанными с ними свободными функциями; см. рекомендацию 57). Избегайте помещения типов в те же пространства имен, что и шаблоны функций или операторов).

Обсуждение

Следуя данному совету, вы сможете избежать трудно обнаруживаемых ошибок в вашем коде и необходимости разбираться с очень тонкими моментами языка, с которыми вы просто не должны сталкиваться.

Вот реальный пример, который был опубликован в группе новостей:

#include <vector>

namespace N {

struct X { };

template<typename T>

int* operator+(T , unsigned) {/* некоторые действия */}

}

int main {

 std::vector<N::X> v(5);

 v[0];

}

Инструкция

v[0];
компилируется в некоторых реализациях стандартной библиотеки, но не во всех. Попробуем кратко пересказать эту длинную историю. Очень тонкая проблема связана с тем, что внутри большинства реализаций
vector<T>::operator[]
спрятан код наподобие
v.begin+n
, и поиск имен для функции
operator+
может достичь пространства имен (в нашем случае
N
) типа, для которого инстанцирован вектор (в нашем случае
X
). Достигнет ли поиск этого пространства имен или нет — зависит от того, как определен
vector<T>::iterator
в данной версии реализации стандартной библиотеки. Однако если поиск достигает
N
, то здесь он находит
N::operator+
. Наконец, в зависимости от используемых типов, компилятор может просто посчитать, что для
vector<T>::iterator
оператор
N::operator+
имеет лучшее соответствие, чем оператор
std::operator+
из реализации стандартной библиотеки (который и должен был быть вызван). (Один из способов избежать такой неприятности в реализации стандартной библиотеки — не использовать код
v.begin+n
таким образом, что он вносит непреднамеренную точку настройки: либо надо изменить код так, чтобы тип
v.begin
никаким образом не зависел от параметра шаблона, либо вызов
operator+
следует переписать с указанием полного квалифицированного имени. См. рекомендацию 65.)

Поделиться с друзьями: