Які хороші пояснення щодо того, який пошук залежить від аргументів? Багато людей також називають його Koenig Lookup.
Переважно я хотів би знати:
- Чому це добре?
- Чому це погано?
- Як це працює?
std::cout << "Hello world";
не
Які хороші пояснення щодо того, який пошук залежить від аргументів? Багато людей також називають його Koenig Lookup.
Переважно я хотів би знати:
std::cout << "Hello world";
не
Відповіді:
Koenig Lookup або Argument Dependent Lookup описує, як некваліфіковані імена шукають компілятор у C ++.
Стандарт C ++ 11, пункт 3.4.2 / 1, визначає:
Коли постфікс-вираз у виклику функції (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
декларації, ні using
-направлення, але все ж компілятор правильно ідентифікує некваліфіковане ім'я doSomething()
як функцію, оголошену в просторі імен MyNamespace
, застосовуючи пошук Koenig .
Алгоритм вказує компілятору не просто дивитися на локальну область, але й простори імен, що містять тип аргументу. Таким чином, у наведеному вище коді компілятор виявляє, що об’єкт obj
, який є аргументом функції doSomething()
, належить до простору імен MyNamespace
. Отже, він дивиться на той простір імен, щоб знайти декларацію doSomething()
.
Як показано на прикладі простого коду, пошук Koenig забезпечує програмісту зручність і простоту використання. Без пошуку Koenig не було б накладних витрат на програміста, щоб неодноразово вказувати повністю кваліфіковані імена або замість цього використовувати численні using
декларації.
Надмірна залежність від пошуку Koenig може призвести до смислових проблем, а програміст іноді застає програватися.
Розглянемо приклад std::swap
, який є стандартним алгоритмом бібліотеки для заміни двох значень. Під час пошуку Koenig потрібно бути обережним при використанні цього алгоритму, оскільки:
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 Визначення пошуку Koenig є таким, як визначено в книзі Йозуттіса, Стандартна бібліотека C ++: Навчальний посібник та довідник .
std::swap
ви дійсно повинні це зробити, оскільки єдиною альтернативою було б додати std::swap
чітку спеціалізацію функції шаблону для вашого A
класу. Але якщо ваш A
клас є самим шаблоном, це буде часткова спеціалізація, а не явна спеціалізація. І часткова спеціалізація функції шаблону не допускається. Додавання перевантаження з боку std::swap
було б альтернативою, але явно заборонено (ви не можете додавати речі до std
простору імен). Тож ADL - єдиний спосіб std::swap
.
std::swap()
здається трохи назад. Я б очікував, що проблема буде в тому випадку, коли std::swap()
вибрано, а не перевантаження, специфічну для типу A::swap()
,. Приклад з std::swap(A::MyClass&, A::MyClass&)
здається оманливим. оскільки std
ніколи не було б конкретної перевантаження для типу користувача, я не думаю, що це чудовий приклад.
MyNamespace::doSomething
, а не просто ::doSomething
.
Якщо в Koenig Lookup, якщо функція викликається без вказівки її простору імен, то ім'я функції також шукається у просторі імен, у яких визначено тип аргументів (аргументів). Ось чому він також відомий як аргументозалежне ім'я пошуку , коротше просто ADL .
Через Lookup Koenig ми можемо написати це:
std::cout << "Hello World!" << "\n";
В іншому випадку нам доведеться написати:
std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
що насправді занадто багато набирає, і код виглядає дуже потворно!
Іншими словами, за відсутності Koenig Lookup навіть програма Hello World виглядає складною.
std::cout
це один аргумент функції, достатній для включення ADL. Ви це помітили?
ostream<<
(як у тому, що він бере як аргументи і що він повертає). 2) Повністю кваліфіковані імена (наприклад, std::vector
або std::operator<<
). 3) Більш детальне вивчення залежної від аргументації пошуку.
std::endl
бути аргументом, насправді є функцією-членом. У будь-якому випадку, якщо я використовую "\n"
замість цього std::endl
, то моя відповідь правильна. Дякуємо за коментар
f(a,b)
викликає вільну функцію. Так що у випадку std::operator<<(std::cout, std::endl);
, немає такої вільної функції, яка бере std::endl
другий аргумент. Це функція-член, яка береться std::endl
за аргумент, і яку ви повинні написати std::cout.operator<<(std::endl);
. і оскільки існує вільна функція, яка береться char const*
за другий аргумент, "\n"
працює; '\n'
працювали б також.
Можливо, найкраще почати з того, чому, і лише потім переходити до того, як.
Коли вводилися простори імен, ідея полягала в тому, щоб все було визначено в просторах імен, щоб окремі бібліотеки не заважали одна одній. Однак це ввело проблему з операторами. Шукайте, наприклад, наступний код:
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)
in main()
.
Не все з цього приводу добре, на мою думку. Люди, включаючи постачальників компіляторів, ображали це через його іноді нещасну поведінку.
ADL несе відповідальність за капітальний ремонт петлі дальності в С ++ 11. Щоб зрозуміти, чому ADL іноді може мати ненавмисні ефекти, врахуйте, що враховуються не лише простори імен, де визначені аргументи, але й аргументи шаблонів аргументів, типів параметрів типів функцій / типів pointee типів покажчиків цих аргументів і так далі.
Приклад використання прискорення
std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);
Це призвело до неоднозначності, якщо користувач використовує бібліотеку boost.range, оскільки обидва std::begin
знаходять (за допомогою ADL std::vector
) та boost::begin
знаходять (використовуючи ADL boost::shared_ptr
).
std::begin
очищує двозначність простору імен.