Was sind einige gute Erklärungen dafür, was argumentabhängige Suche ist? Viele Leute nennen es auch Koenig Lookup.
Am liebsten würde ich wissen:
- Warum ist es gut?
- Warum ist es eine schlechte Sache?
- Wie funktioniert es?
Benutzer965369
Was sind einige gute Erklärungen dafür, was argumentabhängige Suche ist? Viele Leute nennen es auch Koenig Lookup.
Am liebsten würde ich wissen:
Alok Speichern
König Lookupoder Argumentabhängige Suchebeschreibt, wie unqualifizierte Namen vom Compiler in C++ nachgeschlagen werden.
Der C++11-Standard § 3.4.2/1 besagt:
Wenn der Postfix-Ausdruck in einem Funktionsaufruf (5.2.2) eine unqualifizierte ID ist, können andere Namensräume, die während der üblichen unqualifizierten Suche (3.4.1) nicht berücksichtigt werden, durchsucht werden, und in diesen Namensräumen werden Namensraumbereichsfreund-Funktionsdeklarationen ( 11.3) nicht anderweitig sichtbar gefunden werden. Diese Änderungen an der Suche hängen von den Typen der Argumente ab (und für Template-Template-Argumente vom Namespace des Template-Arguments).
Einfacher ausgedrückt sagt Nicolai Josuttis1:
Sie müssen den Namensraum für Funktionen nicht qualifizieren, wenn ein oder mehrere Argumenttypen im Namensraum der Funktion definiert sind.
Ein einfaches Codebeispiel:
namespace MyNamespace
{
class MyClass {};
void doSomething(MyClass) {}
}
MyNamespace::MyClass obj; // global object
int main()
{
doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}
Im obigen Beispiel gibt es weder a using
-Erklärung noch a using
-Direktive, aber der Compiler identifiziert den nicht qualifizierten Namen trotzdem korrekt doSomething()
als die im Namespace deklarierte Funktion MyNamespace
durch Auftragen Koenig-Suche.
Der Algorithmus weist den Compiler an, nicht nur den lokalen Gültigkeitsbereich zu betrachten, sondern auch die Namespaces, die den Typ des Arguments enthalten. Somit findet der Compiler im obigen Code, dass das Objekt obj
was das Argument der Funktion ist doSomething()
gehört zum Namensraum MyNamespace
. Es sucht also in diesem Namensraum nach der Deklaration von doSomething()
.
Wie das obige einfache Codebeispiel zeigt, bietet die Koenig-Suche dem Programmierer Komfort und Benutzerfreundlichkeit. Ohne die Koenig-Suche würde es für den Programmierer einen Mehraufwand geben, die vollständig qualifizierten Namen wiederholt anzugeben oder stattdessen zahlreiche zu verwenden using
-Erklärungen.
Sich zu sehr auf die Koenig-Suche zu verlassen, kann zu semantischen Problemen führen und den Programmierer manchmal unvorbereitet treffen.
Betrachten Sie das Beispiel von std::swap
, bei dem es sich um einen Standardbibliotheksalgorithmus zum Austauschen zweier Werte handelt. Bei der Koenig-Suche müsste man bei der Verwendung dieses Algorithmus vorsichtig sein, weil:
std::swap(obj1,obj2);
zeigt möglicherweise nicht das gleiche Verhalten wie:
using std::swap;
swap(obj1, obj2);
Mit ADL, welche Version von swap
Funktion aufgerufen wird, würde vom Namensraum der übergebenen Argumente abhängen.
Wenn es einen Namensraum gibt A
und wenn A::obj1
, A::obj2
und A::swap()
existieren, dann führt das zweite Beispiel zu einem Aufruf an A::swap()
was möglicherweise nicht das ist, was der Benutzer wollte.
Ferner, wenn aus irgendeinem Grund beides A::swap(A::MyClass&, A::MyClass&)
und std::swap(A::MyClass&, A::MyClass&)
definiert sind, dann wird das erste Beispiel aufgerufen std::swap(A::MyClass&, A::MyClass&)
aber die zweite wird nicht kompiliert, weil swap(obj1, obj2)
wäre zweideutig.
Da es von einem ehemaligen Forscher und Programmierer von AT&T und Bell Labs entwickelt wurde, Andreas König.
Standard C++03/11 [basic.lookup.argdep]: 3.4.2 Argumentabhängige Namenssuche.
**1** Die Definition der Koenig-Suche ist wie in Josuttis’ Buch *The C++ Standard Library: A Tutorial and Reference* definiert.
@AlokSave: +1 für die Antwort, aber die Kleinigkeiten sind nicht korrekt. Koenig hat ADL nicht erfunden, as er gesteht hier 🙂
– legends2k
28. Juli 2014 um 7:30 Uhr
Das Beispiel in der Kritik am Koenig-Algorithmus kann sowohl als “Feature” der Koenig-Suche als auch als “Betrug” angesehen werden. Die Verwendung von std::swap() auf diese Weise ist eine gängige Redewendung: Stellen Sie ein ‘using std::swap()’ bereit, falls eine spezialisiertere Version A::swap() nicht bereitgestellt wird. Wenn eine spezialisierte Version von A::swap() verfügbar ist, würden wir normalerweise will der angerufen werden soll. Dies bietet mehr Generizität für den Aufruf von swap(), da wir darauf vertrauen können, dass der Aufruf kompiliert und funktioniert, aber wir können auch darauf vertrauen, dass die spezialisiertere Version verwendet wird, falls es eine gibt.
– Anthony Halle
9. Dezember 2014 um 19:16 Uhr
@anthrond Da steckt mehr drin. Mit std::swap
Sie müssen das tatsächlich tun, da die einzige Alternative das Hinzufügen wäre std::swap
Vorlagenfunktion explizite Spezialisierung für Ihre A
Klasse. Doch wenn Ihr A
Klasse selbst eine Vorlage ist, wäre es eher eine partielle Spezialisierung als eine explizite Spezialisierung. Und eine teilweise Spezialisierung der Vorlagenfunktion ist nicht zulässig. Überladung von hinzufügen std::swap
wäre eine Alternative, ist aber ausdrücklich verboten (Sie dürfen keine Dinge hinzufügen std
Namensraum). Also ADL ist das einzige Weg für std::swap
.
– Adam Badura
10. April 2015 um 11:23 Uhr
Ich hätte erwartet, dass überladene Operatoren unter “Vorteil der Koenig-Suche” erwähnt werden. das Beispiel mit std::swap()
wirkt etwas rückständig. Ich würde erwarten, dass das Problem wann sein wird std::swap()
statt der typspezifischen Überladung ausgewählt wird, A::swap()
. Das Beispiel mit std::swap(A::MyClass&, A::MyClass&)
scheint irreführend. seit std
würde niemals eine spezifische Überladung für einen Benutzertyp haben, ich denke nicht, dass es ein gutes Beispiel ist.
– Arvid
24. Oktober 2016 um 16:26 Uhr
@gsamaras …Und? Wir können alle sehen, dass die Funktion nie definiert wurde. Ihre Fehlermeldung beweist, dass es tatsächlich funktioniert hat, denn es wird gesucht MyNamespace::doSomething
nicht nur ::doSomething
.
– Nik
12. Juni 2018 um 23:47 Uhr
Nawaz
Wenn in der Koenig-Suche eine Funktion aufgerufen wird, ohne ihren Namensraum anzugeben, dann ist der Name einer Funktion Auch gesucht in Namespace(s), in denen der Typ des Arguments/der Argumente definiert ist. Deshalb wird es auch als bezeichnet Argumentabhängige Namenssuchekurz einfach ADL.
Aufgrund von Koenig Lookup können wir Folgendes schreiben:
std::cout << "Hello World!" << "\n";
Sonst müssten wir schreiben:
std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
das ist wirklich zu viel Tippen und der Code sieht wirklich hässlich aus!
Mit anderen Worten, in Ermangelung von Koenig Lookup sogar a Hallo Welt Programm sieht kompliziert aus.
Das Beispiel ist eigentlich falsch. Es wäre richtig, wenn (zum Beispiel) std::string
wurde anstelle von String-Literal verwendet. Siehe Koenigs Erklärung: drdobbs.com/cpp/a-personal-note-about-argument-dependent/…
– Adam Badura
10. April 2015 um 11:54 Uhr
@AdamBadura: Bitte beachten Sie das std::cout
ist ein Argument für die Funktion, das ausreicht, um ADL zu aktivieren. Hast du das gemerkt?
– Nawaz
10. April 2015 um 15:58 Uhr
@meet: Ihre Frage erfordert eine lange Antwort, die an dieser Stelle nicht gegeben werden kann. Ich kann Ihnen also nur raten, sich mit Themen wie: 1) Unterschrift von zu befassen ostream<<
(was es als Argumente braucht und was es zurückgibt). 2) Vollqualifizierte Namen (wie std::vector
oder std::operator<<
). 3) Eine detailliertere Untersuchung des argumentabhängigen Nachschlagens.
– Nawaz
27. Mai 2015 um 3:26 Uhr
@WorldSEnder: Ja, du hast Recht. Die Funktion, die übernehmen kann std::endl
als Argument ist eigentlich eine Member-Funktion. Wie auch immer, wenn ich benutze "\n"
anstatt std::endl
, dann ist meine Antwort richtig. Danke für den Kommentar.
– Nawaz
16. Juli 2015 um 7:04 Uhr
@Destructor: Weil ein Funktionsaufruf der Form von f(a,b)
ruft a auf kostenlos Funktion. Also im Falle von std::operator<<(std::cout, std::endl);
gibt es keine solche freie Funktion, die dauert std::endl
als zweites Argument. Es ist die Mitgliedsfunktion, die übernimmt std::endl
als Argument, und für das Sie schreiben müssen std::cout.operator<<(std::endl);
. und da gibt es a kostenlos Funktion, die dauert char const*
Als zweites Argument "\n"
Werke; '\n'
würde auch funktionieren.
– Nawaz
6. Juni 2016 um 5:42 Uhr
celtschk
Vielleicht ist es am besten, mit dem Warum zu beginnen und erst dann zum Wie überzugehen.
Als Namespaces eingeführt wurden, war die Idee, alles in Namespaces zu definieren, damit separate Bibliotheken sich nicht gegenseitig stören. Dies führte jedoch zu einem Problem mit den Operatoren. Sehen Sie sich zum Beispiel den folgenden Code an:
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
???
}
Natürlich hättest du schreiben können N::operator++(x)
, aber das hätte den eigentlichen Punkt der Operatorüberladung zunichte gemacht. Daher musste eine Lösung gefunden werden, die es dem Compiler ermöglichte, zu finden operator++(X&)
trotz der Tatsache, dass es nicht im Geltungsbereich war. Auf der anderen Seite sollte es immer noch keinen anderen finden operator++
in einem anderen, nicht verwandten Namensraum definiert, wodurch der Aufruf mehrdeutig werden könnte (in diesem einfachen Beispiel würden Sie keine Mehrdeutigkeit erhalten, in komplexeren Beispielen jedoch möglicherweise). Die Lösung war Argument Dependent Lookup (ADL), so genannt, da die Suche vom Argument abhängt (genauer gesagt vom Typ des Arguments). Da das Schema von Andrew R. Koenig erfunden wurde, wird es auch oft Koenig-Lookup genannt.
Der Trick besteht darin, dass für Funktionsaufrufe zusätzlich zur normalen Namenssuche (die Namen im Geltungsbereich zum Zeitpunkt der Verwendung findet) eine zweite Suche in den Geltungsbereichen der Typen aller an die Funktion übergebenen Argumente durchgeführt wird. Also im obigen Beispiel, wenn Sie schreiben x++
in der Hauptsache sucht es operator++
nicht nur im globalen Geltungsbereich, sondern auch in dem Geltungsbereich, in dem der Typ von x
, N::X
wurde definiert, dh in namespace N
. Und dort findet es ein passendes operator++
und deshalb x++
funktioniert einfach. Andere operator++
in einem anderen Namensraum definiert, sagen wir N2
, wird jedoch nicht gefunden. Da ADL nicht auf Namensräume beschränkt ist, können Sie auch verwenden f(x)
anstatt N::f(x)
in main()
.
Danke! Ich habe nie wirklich verstanden, warum es dort war!
– Benutzer965369
13. November 2011 um 15:25 Uhr
Nicht alles daran ist meiner Meinung nach gut. Leute, einschließlich Compiler-Anbieter, haben es wegen seines manchmal unglücklichen Verhaltens beleidigt.
ADL ist für eine umfassende Überarbeitung der for-Range-Schleife in C++11 verantwortlich. Um zu verstehen, warum ADL manchmal unbeabsichtigte Auswirkungen haben kann, bedenken Sie, dass nicht nur die Namespaces, in denen die Argumente definiert sind, berücksichtigt werden, sondern auch die Argumente von Template-Argumenten der Argumente, von Parametertypen von Funktionstypen / Pointee-Typen von Zeigertypen dieser Argumente , und so weiter und fort.
Ein Beispiel mit Boost
std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);
Dies führte zu einer Unklarheit, wenn der Benutzer die boost.range-Bibliothek verwendet, da beide std::begin
gefunden wird (von ADL mit std::vector
) und boost::begin
gefunden wird (von ADL mit boost::shared_ptr
).
gotw.ca/gotw/030.htm
– Flexo
♦
13. November 2011 um 13:17 Uhr
mögliches Duplikat von Why GCC erlaubt den Aufruf dieser Funktion, ohne zuerst ihren Namespace zu verwenden?
– sehen
13. November 2011 um 13:28 Uhr
Das ist gut so, denn sonst:
std::cout << "Hello world";
würde nicht kompilieren– sehen
13. November 2011 um 13:30 Uhr
de.cppreference.com/w/cpp/language/adl
– Li Kui
19. März 2018 um 9:39 Uhr