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

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+ окажется, к несчастью, вполне подходящим с точки зрения компилятора, и программа скомпилируется успешно, но вот результаты ее работы могут оказаться совершенно неожиданными...

Вы думаете, что вам не приходилось с этим сталкиваться? Попробуйте вспомнить, бывало ли такое в вашей практике, что ваш код, например, с использованием стандартной библиотеки приводил к удивительным и непонятным ошибкам компиляции? А после того как вы слегка меняли ваш код, порой просто меняя местами отдельные куски кода, все вдруг начинало работать и у вас оставалось только небольшое недоумение по поводу глупого компилятора, который запутался в трех строках? Практически все мы попадали в подобные ситуации, когда причиной неприятностей становилась рассматриваемая проблема, т.е. когда ADL находил имена из неподходящего пространства имен просто потому, что типы из этих пространств имен использовались поблизости друг от друга.

Эта проблема возникает не только при использовании стандартной библиотеки. В С++ с ней можно столкнуться (и это часто происходит на практике) при использовании типов, определенных в тех же пространствах имен, что и функции (в особенности шаблоны функций или операторы), не связанные с данными типами. Постарайтесь не попадаться в эту ловушку.

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

Примечание. Да, стандартная библиотека С++ помещает алгоритмы и другие шаблоны функций, таких как copy или distance, в то же пространство имен, что и множество типов, таких как pair или vector. Все они находятся в одном пространстве имен. Это неудачное решение, которое вызывает описанные весьма тонкие и трудно локализуемые проблемы. К счастью, теперь у нас больше опыта и мы знаем, как следует поступать. Не делайте так, как сделано в стандартной библиотеке.

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

Ссылки

[Stroustrup00] §10.3.2, §11.2.4 • [Sutter00] §34 • [Sutter02] §39-40