поставляются вместе с определением 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.
operator<<
и operator>>
для объектов некоторого класса X
, вероятно, относятся к наиболее ярким примерам функций, которые вполне очевидно являются частью интерфейса класса X
, но при этом всегда представляют собой свободные функции (это обязательное условие, поскольку левый аргумент этих операторов — поток, а не объект X
). Та же аргументация применима и к другим операторам, не являющимся членами X
. Убедитесь, что ваши операторы находятся в том же пространстве имен, что и класс, с которым они работают. Если у вас есть возможность выбора, лучше делать операторы и все прочие функции не членами и не друзьями класса (см. рекомендацию 44).
X
предоставляет именованные вспомогательные функции, которые получают в качестве аргументов объекты X
, они должны находиться в том же пространстве имен, что и X
. В противном случае вызывающий код, использующий объекты X
, будет не в состоянии работать с этими именованными функциями без явной квалификации их имен или применения инструкции using
.
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.)
Коротко говоря, вряд ли вам удастся выявить истинную причину выводимого сообщения об ошибке. Если вам, конечно, повезет и вы получите это сообщение об ошибке, так как в случае невезения выбранный оператор N::operator+
окажется, к несчастью, вполне подходящим с точки зрения компилятора, и программа скомпилируется успешно, но вот результаты ее работы могут оказаться совершенно неожиданными...
Вы думаете, что вам не приходилось с этим сталкиваться? Попробуйте вспомнить, бывало ли такое в