C++
Überlastauflösung
Suche…
Bemerkungen
Die Überlastauflösung tritt in verschiedenen Situationen auf
- Ruft benannte überladene Funktionen auf. Die Kandidaten sind alle Funktionen, die durch Namenssuche gefunden werden.
- Aufrufe des Klassenobjekts Die Kandidaten sind normalerweise alle überladenen Funktionsaufrufoperatoren der Klasse.
- Verwendung eines Operators Die Kandidaten sind die überladenen Operatorfunktionen im Namespace-Bereich, die überladenen Operatorfunktionen im linken Klassenobjekt (falls vorhanden) und die integrierten Operatoren.
- Überladungsauflösung, um die korrekte Konvertierungsoperatorfunktion oder den Konstruktor für eine Initialisierung aufzurufen
- Bei der Direktinitialisierung außerhalb der Liste (
Class c(value)
) handelt es sich bei den Kandidaten um Konstruktoren vonClass
. - Für die Nicht-Listenkopie-Initialisierung (
Class c = value
) und für das Auffinden der benutzerdefinierten Konvertierungsfunktion, die in einer benutzerdefinierten Konvertierungssequenz aufgerufen werden soll. Die Kandidaten sind die Konstruktoren vonClass
Wenn die Quelle ein Klassenobjekt ist, fungiert der Konvertierungsoperator. - Zur Initialisierung einer Nicht-Klasse aus einem Klassenobjekt (Nicht-Klasse
Nonclass c = classObject
). Die Kandidaten sind die Konvertierungsoperatorfunktionen des Initialisierungsobjekts. - Zum Initialisieren einer Referenz mit einem Klassenobjekt (
R &r = classObject
), wenn die Klasse über Konvertierungsoperatorfunktionen verfügt, die Werte liefern, die direkt anr
gebunden werden können. Die Kandidaten sind solche Konvertierungsoperatorfunktionen. - Bei der Listeninitialisierung eines nicht aggregierten Klassenobjekts (
Class c{1, 2, 3}
) sind die Kandidaten die Initialisierungslistenkonstruktoren für eine Auflösung des ersten Durchlaufs durch Überladung. Wenn dies keinen geeigneten Kandidaten findet, wird eine zweite Überlastungslösung mit den Konstruktoren vonClass
als Kandidaten durchgeführt.
- Bei der Direktinitialisierung außerhalb der Liste (
Genaue Übereinstimmung
Eine Überladung ohne Konvertierungen, die für Parametertypen erforderlich sind, oder nur Konvertierungen, die zwischen Typen bestehen, die noch als exakte Übereinstimmungen betrachtet werden, wird gegenüber einer Überladung bevorzugt, für deren Aufruf andere Konvertierungen erforderlich sind.
void f(int x);
void f(double x);
f(42); // calls f(int)
Wenn ein Argument an einen Verweis auf den gleichen Typ gebunden wird, wird davon ausgegangen, dass die Übereinstimmung keine Konvertierung erfordert, selbst wenn der Verweis mehr Cv-qualifiziert ist.
void f(int& x);
void f(double x);
int x = 42;
f(x); // argument type is int; exact match with int&
void g(const int& x);
void g(int x);
g(x); // ambiguous; both overloads give exact match
Zum Zwecke der Überladungsauflösung wird davon ausgegangen, dass der Typ "Array von T
" genau mit dem Typ "Zeiger auf T
" übereinstimmt, und der Funktionstyp T
stimmt mit dem Funktionszeigertyp T*
genau überein, obwohl beide dies erfordern Konvertierungen.
void f(int* p);
void f(void* p);
void g(int* p);
void g(int (&p)[100]);
int a[100];
f(a); // calls f(int*); exact match with array-to-pointer conversion
g(a); // ambiguous; both overloads give exact match
Kategorisierung von Argumenten zu Parameterkosten
Die Überladungsauflösung partitioniert die Kosten für die Übergabe eines Arguments an einen Parameter in eine von vier verschiedenen Kategorien, die als "Sequenzen" bezeichnet werden. Jede Sequenz kann null, eine oder mehrere Konvertierungen enthalten
Standard-Konvertierungssequenz
void f(int a); f(42);
Benutzerdefinierte Konvertierungssequenz
void f(std::string s); f("hello");
Ellipsis-Konvertierungssequenz
void f(...); f(42);
Initialisierungssequenz auflisten
void f(std::vector<int> v); f({1, 2, 3});
Das allgemeine Prinzip ist, dass Standard-Konvertierungssequenzen am billigsten sind, gefolgt von benutzerdefinierten Konvertierungssequenzen, gefolgt von Ellipsis-Konvertierungssequenzen.
Ein Sonderfall ist die Listeninitialisierungssequenz, die keine Konvertierung darstellt (eine Initialisierungsliste ist kein Ausdruck mit einem Typ). Ihre Kosten werden bestimmt, indem definiert wird, dass sie einer der anderen drei Konvertierungssequenzen entspricht, abhängig vom Parametertyp und der Form der Initialisierungsliste.
Namenssuche und Zugriffsprüfung
Die Überlastauflösung erfolgt nach der Namenssuche. Das bedeutet, dass eine besser passende Funktion nicht durch Überladungsauflösung ausgewählt wird, wenn die Namenssuche verloren geht:
void f(int x);
struct S {
void f(double x);
void g() { f(42); } // calls S::f because global f is not visible here,
// even though it would be a better match
};
Die Überlastauflösung erfolgt vor der Zugriffsprüfung. Eine nicht zugreifbare Funktion kann durch Überlastauflösung ausgewählt werden, wenn sie besser als eine zugreifbare Funktion ist.
class C {
public:
static void f(double x);
private:
static void f(int x);
};
C::f(42); // Error! Calls private C::f(int) even though public C::f(double) is viable.
In ähnlicher Weise erfolgt die Überlastlösung, ohne zu prüfen, ob der resultierende Aufruf in Bezug auf explicit
:
struct X {
explicit X(int );
X(char );
};
void foo(X );
foo({4}); // X(int) is better much, but expression is
// ill-formed because selected constructor is explicit
Überladen der Weiterleitungsreferenz
Sie müssen beim Übermitteln einer Weiterleitungsreferenz sehr vorsichtig sein, da dies möglicherweise zu gut passt:
struct A {
A() = default; // #1
A(A const& ) = default; // #2
template <class T>
A(T&& ); // #3
};
Die Absicht hier war, dass A
kopierbar ist und dass wir diesen anderen Konstruktor haben, der ein anderes Member initialisieren könnte. Jedoch:
A a; // calls #1
A b(a); // calls #3!
Es gibt zwei mögliche Übereinstimmungen für den Bauaufruf:
A(A const& ); // #2
A(A& ); // #3, with T = A&
Beide sind genaue Übereinstimmung, aber #3
nimmt einen Verweis auf eine weniger cv , Qualifizierte Objekt als #2
der Fall ist, so hat es die bessere Standardumwandlungsfolge und ist die beste tragfähige Funktion.
Die Lösung hier ist, diese Konstruktoren immer einzuschränken (z. B. mit SFINAE):
template <class T,
class = std::enable_if_t<!std::is_convertible<std::decay_t<T>*, A*>::value>
>
A(T&& );
Das Typmerkmal besteht darin, ein beliebiges A
oder eine Klasse, die öffentlich und eindeutig von A
abgeleitet ist, von der Betrachtung auszuschließen, was dazu führen würde, dass dieser Konstruktor in dem zuvor beschriebenen Beispiel falsch geformt wurde (und daher aus dem Überlastungssatz entfernt wurde). Als Ergebnis wird der Kopierkonstruktor aufgerufen - was wir wollten.
Schritte zur Überlastauflösung
Die Schritte der Überlastlösung sind:
Finden Sie Kandidatenfunktionen über die Namenssuche. Unqualifizierte Aufrufe führen sowohl eine regelmäßige unqualifizierte Suche als auch eine argumentabhängige Suche (falls zutreffend) durch.
Filtern Sie die Menge der möglichen Funktionen nach einer Reihe praktikabler Funktionen. Eine realisierbare Funktion, für die eine implizite Konvertierungssequenz zwischen den Argumenten, mit denen die Funktion aufgerufen wird, und den von der Funktion verwendeten Parametern vorhanden ist.
void f(char); // (1) void f(int ) = delete; // (2) void f(); // (3) void f(int& ); // (4) f(4); // 1,2 are viable (even though 2 is deleted!) // 3 is not viable because the argument lists don't match // 4 is not viable because we cannot bind a temporary to // a non-const lvalue reference
Wählen Sie den besten Kandidaten aus. Eine realisierbare Funktion
F1
ist eine bessere Funktion als eine andere realisierbare FunktionF2
wenn die implizite Konvertierungssequenz für jedes Argument inF1
nicht schlechter als die entsprechende implizite Konvertierungssequenz inF2
ist und ...:3.1. Für einige Argumente ist die implizite Konvertierungssequenz für dieses Argument in
F1
eine bessere Konvertierungssequenz als für dieses Argument inF2
odervoid f(int ); // (1) void f(char ); // (2) f(4); // call (1), better conversion sequence
3.2. Bei einer benutzerdefinierten Konvertierung ist die Standardkonvertierungssequenz von der Rückkehr von
F1
zum Zieltyp eine bessere Konvertierungssequenz als die des Rückgabetyps vonF2
oderstruct A { operator int(); operator double(); } a; int i = a; // a.operator int() is better than a.operator double() and a conversion float f = a; // ambiguous
3.3. In einer direkten Referenzbindung hat
F1
die gleiche Art von Referenz vonF2
nicht oder nichtstruct A { operator X&(); // #1 operator X&&(); // #2 }; A a; X& lx = a; // calls #1 X&& rx = a; // calls #2
3.4.
F1
ist keine Funktionsvorlagenspezialisierung, aberF2
ist odertemplate <class T> void f(T ); // #1 void f(int ); // #2 f(42); // calls #2, the non-template
3,5.
F1
undF2
sind beide Funktionsschablonenspezialisierungen, aberF1
ist spezialisierter alsF2
.template <class T> void f(T ); // #1 template <class T> void f(T* ); // #2 int* p; f(p); // calls #2, more specialized
Die Reihenfolge hier ist signifikant. Die bessere Prüfung der Konvertierungssequenz findet vor der Vorlage statt der Prüfung ohne Vorlage statt. Dies führt zu einem allgemeinen Fehler bei der Überladung der Weiterleitungsreferenz:
struct A {
A(A const& ); // #1
template <class T>
A(T&& ); // #2, not constrained
};
A a;
A b(a); // calls #2!
// #1 is not a template but #2 resolves to
// A(A& ), which is a less cv-qualified reference than #1
// which makes it a better implicit conversion sequence
Wenn am Ende kein einziger bester Kandidat vorhanden ist, ist der Anruf mehrdeutig:
void f(double ) { }
void f(float ) { }
f(42); // error: ambiguous
Arithmetische Promotionen und Konvertierungen
Das Konvertieren eines Integer-Typs in den entsprechenden heraufgestuften Typ ist besser als das Konvertieren in einen anderen Integer-Typ.
void f(int x);
void f(short x);
signed char c = 42;
f(c); // calls f(int); promotion to int is better than conversion to short
short s = 42;
f(s); // calls f(short); exact match is better than promotion to int
Es ist besser, einen float
in das double
, als ihn in einen anderen Gleitkommatyp zu konvertieren.
void f(double x);
void f(long double x);
f(3.14f); // calls f(double); promotion to double is better than conversion to long double
Andere arithmetische Konvertierungen als Beförderungen sind weder besser noch schlechter als einander.
void f(float x);
void f(long double x);
f(3.14); // ambiguous
void g(long x);
void g(long double x);
g(42); // ambiguous
g(3.14); // ambiguous
Um sicherzustellen, dass beim Aufruf einer Funktion f
mit Integral- oder Gleitkomma-Argumenten eines beliebigen Standardtyps keine Mehrdeutigkeit auftritt, sind insgesamt acht Überladungen erforderlich, sodass für jeden möglichen Argumenttyp entweder eine Überladung passt genau oder die eindeutige Überladung mit dem beförderten Argumenttyp wird ausgewählt.
void f(int x);
void f(unsigned int x);
void f(long x);
void f(unsigned long x);
void f(long long x);
void f(unsigned long long x);
void f(double x);
void f(long double x);
Überladen innerhalb einer Klassenhierarchie
In den folgenden Beispielen wird diese Klassenhierarchie verwendet:
struct A { int m; };
struct B : A {};
struct C : B {};
Die Konvertierung vom abgeleiteten Klassentyp in den Basisklassentyp wird gegenüber benutzerdefinierten Konvertierungen bevorzugt. Dies gilt bei der Übergabe als Wert oder Referenz, sowie bei der Konvertierung von Zeiger in abgeleitete in Zeiger in Basis.
struct Unrelated {
Unrelated(B b);
};
void f(A a);
void f(Unrelated u);
B b;
f(b); // calls f(A)
Eine Zeigerkonvertierung von einer abgeleiteten Klasse in eine Basisklasse ist auch besser als eine Konvertierung in void*
.
void f(A* p);
void f(void* p);
B b;
f(&b); // calls f(A*)
Wenn mehrere Überladungen in derselben Vererbungskette vorhanden sind, wird die am stärksten abgeleitete Basisklassenüberladung bevorzugt. Dies basiert auf einem ähnlichen Prinzip wie der virtuelle Versand: Die "spezialisierteste" Implementierung wird ausgewählt. Die Überladungsauflösung tritt jedoch immer zur Kompilierzeit auf und wird niemals implizit herabgesetzt.
void f(const A& a);
void f(const B& b);
C c;
f(c); // calls f(const B&)
B b;
A& r = b;
f(r); // calls f(const A&); the f(const B&) overload is not viable
Für Zeiger auf Member, die der Klasse zuwiderlaufen, gilt eine ähnliche Regel in umgekehrter Richtung: Die am wenigsten abgeleitete abgeleitete Klasse wird bevorzugt.
void f(int B::*p);
void f(int C::*p);
int A::*p = &A::m;
f(p); // calls f(int B::*)
Überlastung von Konstanz und Volatilität
Die Übergabe eines Zeigerarguments an einen T*
-Parameter ist, wenn möglich, besser als die Übergabe an einen const T*
-Parameter.
struct Base {};
struct Derived : Base {};
void f(Base* pb);
void f(const Base* pb);
void f(const Derived* pd);
void f(bool b);
Base b;
f(&b); // f(Base*) is better than f(const Base*)
Derived d;
f(&d); // f(const Derived*) is better than f(Base*) though;
// constness is only a "tie-breaker" rule
Ebenso ist das Übergeben eines Arguments an einen T&
Parameter, wenn möglich, besser als ein Übergeben an einen const T&
Parameter, auch wenn beide einen genauen Übereinstimmungsrang haben.
void f(int& r);
void f(const int& r);
int x;
f(x); // both overloads match exactly, but f(int&) is still better
const int y = 42;
f(y); // only f(const int&) is viable
Diese Regel gilt auch für const-qualifizierte Memberfunktionen, bei denen es wichtig ist, den veränderbaren Zugriff auf Nicht-const-Objekte und den unveränderlichen Zugriff auf const-Objekte zuzulassen.
class IntVector {
public:
// ...
int* data() { return m_data; }
const int* data() const { return m_data; }
private:
// ...
int* m_data;
};
IntVector v1;
int* data1 = v1.data(); // Vector::data() is better than Vector::data() const;
// data1 can be used to modify the vector's data
const IntVector v2;
const int* data2 = v2.data(); // only Vector::data() const is viable;
// data2 can't be used to modify the vector's data
In gleicher Weise wird eine flüchtige Überlastung weniger bevorzugt als eine nichtflüchtige Überlastung.
class AtomicInt {
public:
// ...
int load();
int load() volatile;
private:
// ...
};
AtomicInt a1;
a1.load(); // non-volatile overload preferred; no side effect
volatile AtomicInt a2;
a2.load(); // only volatile overload is viable; side effect
static_cast<volatile AtomicInt&>(a1).load(); // force volatile semantics for a1