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 von Class .
    • 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 von Class 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 an r 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 von Class als Kandidaten durchgeführt.

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:

  1. 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.

  2. 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
    
  3. Wählen Sie den besten Kandidaten aus. Eine realisierbare Funktion F1 ist eine bessere Funktion als eine andere realisierbare Funktion F2 wenn die implizite Konvertierungssequenz für jedes Argument in F1 nicht schlechter als die entsprechende implizite Konvertierungssequenz in F2 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 in F2 oder

    void 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 von F2 oder

    struct 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 von F2 nicht oder nicht

    struct 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, aber F2 ist oder

    template <class T> void f(T ); // #1
    void f(int );                  // #2
    
    f(42); // calls #2, the non-template
    

    3,5. F1 und F2 sind beide Funktionsschablonenspezialisierungen, aber F1 ist spezialisierter als F2 .

    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


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow