Каковы некоторые хорошие объяснения того, какой аргумент зависит от поиска? Многие люди также называют это Koenig Lookup.
Желательно я хотел бы знать:
- Почему это хорошо?
- Почему это плохо?
- Как это работает?
Каковы некоторые хорошие объяснения того, какой аргумент зависит от поиска? Многие люди также называют это Koenig Lookup.
Желательно я хотел бы знать:
Lookup Koenig, или Argument Dependent Lookup, описывает, как неквалифицированные имена ищутся компилятором в C++.
Стандарт C++ 11 § 3.4.2/1 гласит:
Когда выражение postfix в вызове функции (5.2.2) является безусловным идентификатором, могут быть найдены другие пространства имен, не учитываемые во время обычного безусловного поиска (3.4.1), и в этих пространствах имен объявления функций друзей области видимости области пространства ( 11.3) может быть найдено не видимое иное. Эти модификации поиска зависят от типов аргументов (и для аргументов шаблона шаблона - пространства имен аргумента шаблона).
В более простых сроках Николай Йосуттис заявляет 1:
Вам не нужно указывать пространство имен для функций, если в пространстве имен функции определен один или несколько типов аргументов.
Простой пример кода:
namespace MyNamespace
{
class MyClass {};
void doSomething(MyClass);
}
MyNamespace::MyClass obj; // global object
int main()
{
doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}
В приведенном выше примере нет ни using
-declaration, ни using
-directive, но все же компилятор правильно идентифицирует неквалифицированное имя doSomething()
как функцию, объявленную в пространстве имен MyNamespace
путем применения поиска Кенига.
Алгоритм говорит компилятору не только смотреть на локальную область видимости, но также на пространства имен, которые содержат тип аргумента. Таким образом, в приведенном выше коде компилятор обнаруживает, что объект obj
, являющийся аргументом функции doSomething()
, принадлежит пространству имен MyNamespace
. Таким образом, он смотрит на это пространство имен, чтобы найти объявление doSomething()
.
Как показывает приведенный выше пример простого кода, поиск по Кенигу обеспечивает удобство и простоту использования для программиста. Без поиска Кенига программисту потребовалось бы много времени, чтобы повторно указывать полностью определенные имена или вместо этого использовать многочисленные, using
-declaration s.
Чрезмерная зависимость от поиска Кенига может привести к семантическим проблемам и иногда застать программиста врасплох.
Рассмотрим пример std::swap
, который является стандартным библиотечным алгоритмом для обмена двумя значениями. С поиском Кенига нужно было бы быть осторожным при использовании этого алгоритма, потому что:
std::swap(obj1,obj2);
может не показывать такое же поведение как:
using std::swap;
swap(obj1, obj2);
В случае ADL, какая версия функции swap
будет вызвана, будет зависеть от пространства имен передаваемых ей аргументов.
Если существует пространство имен A
и если A::obj1
, A::obj2
& A::swap()
существуют, то второй пример приведет к вызову A::swap()
, который может не соответствовать желанию пользователя,
Кроме того, если по какой-либо причине определены как A::swap(A::MyClass&, A::MyClass&)
и std::swap(A::MyClass&, A::MyClass&)
, то первый пример вызовет std::swap(A::MyClass&, A::MyClass&)
но второй не будет компилироваться, потому что swap(obj1, obj2)
будет неоднозначным.
Потому что он был разработан бывшим исследователем и программистом AT & T и Bell Labs Эндрю Кенигом.
Стандарт C++ 03/11 [basic.lookup.argdep]: 3.4.2 Поиск имени в зависимости от аргумента.
1 Определение поиска Кенига определено в книге Йосуттиса "Стандартная библиотека C++: учебное пособие и справочник".
В приложении Koenig Lookup, если функция вызывается без указания ее пространства имен, тогда имя функции также выполняется в пространстве имен (имен), в котором определен тип аргумента (ов). Вот почему он также известен как " Поиск аргумента", "Поиск", короче говоря, просто ADL.
Это из-за Koenig Lookup, мы можем написать следующее:
std::cout << "Hello World!" << "\n";
В противном случае нам пришлось бы написать:
std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
который на самом деле слишком много печатает, и код выглядит действительно уродливым!
Другими словами, в случае отсутствия Koenig Lookup даже программа Hello World выглядит сложной.
Может быть, лучше начать с того, почему, и только потом перейти к тому, как.
Когда были введены пространства имен, идея заключалась в том, чтобы все было определено в пространствах имен, так что отдельные библиотеки не мешали друг другу. Однако это привело к проблеме с операторами. Посмотрите, например, на следующий код:
namespace N
{
class X {};
void f(X);
X& operator++(X&);
}
int main()
{
// define an object of type X
N::X x;
// apply f to it
N::f(x);
// apply operator++ to it
???
}
Конечно, вы могли бы написать N::operator++(x)
, но это могло бы победить всю точку перегрузки оператора. Поэтому необходимо найти решение, которое позволило компилятору найти operator++(X&)
, несмотря на то, что он не был в области видимости. С другой стороны, он еще не должен найти другой operator++
, определенный в другом, несвязаном пространстве имен, который может сделать вызов неоднозначным (в этом простом примере вы не получите двусмысленности, но в более сложных примерах можете). Это решение было зависимым от аргументов (ADL), называемое таким образом, поскольку поиск зависит от аргумента (точнее, от типа аргумента). Поскольку схема была изобретена Эндрю Р. Кенигом, ее также часто называют поиском Koenig.
Фокус в том, что для вызовов функций, помимо обычного поиска имени (который находит имена в области в точке использования), выполняется второй поиск в областях типов любых аргументов, заданных этой функции. Поэтому в приведенном выше примере, если вы пишете x++
в основном, он ищет operator++
не только в глобальной области видимости, но дополнительно в области, где был определен тип x
, N::X
, то есть в namespace N
. И там он находит соответствующий operator++
, и поэтому x++
просто работает. Однако другой operator++
, определенный в другом пространстве имен, например N2
, не будет найден. Поскольку ADL не ограничивается пространствами имен, вы также можете использовать f(x)
вместо N::f(x)
в main()
.
Не все это хорошо, на мой взгляд. Люди, в том числе производители компиляторов, оскорбляли его из-за его иногда неудачного поведения.
ADL отвечает за капитальный ремонт цикла for-range в С++ 11. Чтобы понять, почему ADL иногда может иметь непреднамеренные эффекты, учитывайте, что рассматриваются не только пространства имен, где определены аргументы, но также аргументы аргументов шаблона аргументов, типы параметров типов функций/типов указателей типов указателей этих аргументов, и так далее.
Пример использования boost
std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);
Это привело к двусмысленности, если пользователь использует библиотеку boost.range, потому что найдены как std::begin
(с помощью ADL с использованием std::vector
), так и boost::begin
(с помощью ADL с использованием boost::shared_ptr
).