Zoeken…


Opmerkingen

Overbelastingsresolutie gebeurt in verschillende situaties

  • Roept naar overbelaste functies met de naam. De kandidaten zijn alle functies gevonden op naam opzoeken.
  • Oproepen naar klasseobject. De kandidaten zijn meestal alle overbelaste functie-oproepoperators van de klas.
  • Gebruik van een operator. De kandidaten zijn de overbelaste operatorfuncties op naamruimtebereik, de overbelaste operatorfuncties in het linker klasseobject (indien aanwezig) en de ingebouwde operators.
  • Overbelastingsresolutie om de juiste conversieoperatorfunctie of constructor te vinden voor een initialisatie
    • Voor niet-lijst directe initialisatie ( Class c(value) ) zijn de kandidaten constructeurs van Class .
    • Voor niet-lijst kopieerinitialisatie ( Class c = value ) en voor het vinden van de door de gebruiker gedefinieerde conversiefunctie om in een door de gebruiker gedefinieerde conversie te activeren. De kandidaten zijn de constructors van Class en als de bron een class-object is, functioneert de conversie-operator.
    • Voor initialisatie van een niet-klasse uit een Nonclass c = classObject ( Nonclass c = classObject ). De kandidaten zijn de conversieoperatorfuncties van het initialisatieobject.
    • Voor het initialiseren van een referentie met een R &r = classObject ( R &r = classObject ), wanneer de klasse conversieoperatorfuncties heeft die waarden opleveren die direct aan r kunnen worden gebonden. De kandidaten zijn dergelijke conversie-operatorfuncties.
    • Voor lijstinitialisatie van een niet-geaggregeerd klasseobject ( Class c{1, 2, 3} ) zijn de kandidaten de constructeurs van de initialisatielijst voor een first-pass via overbelastingsresolutie. Als dit geen haalbare kandidaat vindt, wordt er een tweede doorgang gedaan via overbelastingsresolutie, met de constructeurs van Class als kandidaten.

Exacte overeenkomst

Een overbelasting zonder conversies die nodig is voor parametertypen of alleen conversies die nodig zijn tussen typen die nog steeds als exacte overeenkomsten worden beschouwd, heeft de voorkeur boven een overbelasting die andere conversies vereist om te kunnen bellen.

void f(int x);
void f(double x);
f(42); // calls f(int)

Wanneer een argument bindt aan een verwijzing naar hetzelfde type, wordt de overeenkomst geacht geen conversie te vereisen, zelfs als de verwijzing meer cv-gekwalificeerd is.

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

Ten behoeve van de overbelastingsresolutie wordt het type "array van T " geacht exact overeen te komen met het type "pointer naar T ", en wordt het functietype T geacht exact overeen te komen met het functiepointer type T* , hoewel beide vereisen conversies.

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

Indeling van argument naar parameterkosten

Resolutie voor overbelasting verdeelt de kosten van het doorgeven van een argument aan een parameter in een van de vier verschillende categorieën, "sequenties" genoemd. Elke reeks kan nul, één of meerdere conversies bevatten

  • Standaard conversie volgorde

    void f(int a); f(42);
    
  • Door de gebruiker gedefinieerde conversie

    void f(std::string s); f("hello");
    
  • Ellipsis conversie volgorde

    void f(...); f(42);
    
  • Lijst initialisatie volgorde

    void f(std::vector<int> v); f({1, 2, 3});
    

Het algemene principe is dat standaardconversiesequenties het goedkoopst zijn, gevolgd door door de gebruiker gedefinieerde conversiesequenties, gevolgd door ellipsisconversiesequenties.

Een speciaal geval is de initialisatie van de lijst, die geen conversie vormt (een initialisatielijst is geen uitdrukking met een type). De kosten worden bepaald door te definiëren dat het equivalent is aan een van de andere drie conversie-reeksen, afhankelijk van het parametertype en de vorm van de initialisatielijst.

Naam opzoeken en toegangscontrole

Overbelastingsresolutie treedt op na het opzoeken van de naam. Dit betekent dat een beter passende functie niet wordt geselecteerd door overbelastingsresolutie als de naam niet meer wordt opgezocht:

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
};

Overbelastingsresolutie treedt op vóór toegangscontrole. Een ontoegankelijke functie kan worden geselecteerd door overbelastingsresolutie als deze beter overeenkomt dan een toegankelijke functie.

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.

Evenzo gebeurt overbelastingsresolutie zonder te controleren of de resulterende oproep goed gevormd is met betrekking tot 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

Overbelasting op doorstuurreferentie

Je moet heel voorzichtig zijn bij het verstrekken van een referentie-overbelasting voor doorsturen, omdat deze te goed kan overeenkomen:

struct A {
    A() = default;           // #1
    A(A const& ) = default;  // #2

    template <class T>
    A(T&& );                 // #3
};

De bedoeling was dat A kan worden gekopieerd en dat we deze andere constructor hebben die een ander lid kan initialiseren. Echter:

A a;     // calls #1
A b(a);  // calls #3!

Er zijn twee uitvoerbare wedstrijden voor de bouwoproep:

A(A const& ); // #2
A(A& );       // #3, with T = A&

Beide zijn Exacte overeenkomsten, maar #3 verwijst naar een minder cv- gekwalificeerd object dan #2 , dus het heeft de betere standaardconversiesequentie en is de best haalbare functie.

De oplossing hier is om deze constructors altijd te beperken (bijvoorbeeld door SFINAE te gebruiken):

template <class T,
    class = std::enable_if_t<!std::is_convertible<std::decay_t<T>*, A*>::value>
    >
A(T&& );

Het type kenmerk hier is om elke A of klasse publiekelijk en ondubbelzinnig afgeleid van A uit overweging te nemen, waardoor deze constructor slecht gevormd zou worden in het eerder beschreven voorbeeld (en dus verwijderd uit de overbelastingsset). Als gevolg hiervan wordt de kopie-constructor aangeroepen - wat we wilden.

Stappen van overbelastingsresolutie

De stappen van overbelastingsresolutie zijn:

  1. Zoek kandidaatfuncties via naam opzoeken. Niet-gekwalificeerde oproepen voeren zowel reguliere niet-gekwalificeerde opzoekopdrachten als argumentafhankelijke opzoekopdrachten uit (indien van toepassing).

  2. Filter de set kandidaatfuncties naar een set uitvoerbare functies. Een uitvoerbare functie waarvoor een impliciete conversie bestaat tussen de argumenten waarmee de functie wordt aangeroepen en de parameters die de functie aanneemt.

    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. Kies de best haalbare kandidaat. Een uitvoerbare functie F1 is een betere functie dan een andere uitvoerbare functie F2 als de impliciete conversiesequentie voor elk argument in F1 niet slechter is dan de overeenkomstige impliciete conversiesequentie in F2 , en ...:

    3.1. Voor sommige argumenten is de impliciete conversiesequentie voor dat argument in F1 een betere conversiesequentie dan voor dat argument in F2 , of

    void f(int );  // (1)
    void f(char ); // (2)
    
    f(4);  // call (1), better conversion sequence
    

    3.2. In een door de gebruiker gedefinieerde conversie is de standaardconversiereeks van de terugkeer van F1 naar het bestemmingstype een betere conversie-volgorde dan die van het retourtype van F2 , of

    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 een directe referentiebinding heeft F1 dezelfde soort referentie door F2 niet, of

    struct A 
    {
        operator X&();  // #1
        operator X&&(); // #2
    };
    A a;
    X& lx = a;  // calls #1
    X&& rx = a; // calls #2
    

    3.4. F1 is geen functiesjabloon-specialisatie, maar F2 is, of

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

    3.5. F1 en F2 zijn beide functiesjabloon-specialisaties, maar F1 is meer gespecialiseerd dan F2 .

    template <class T> void f(T );  // #1
    template <class T> void f(T* ); // #2
    
    int* p;
    f(p); // calls #2, more specialized
    

De volgorde hier is aanzienlijk. De betere conversie van de volgorde van conversies vindt plaats voordat de sjabloon versus niet-sjablooncontrole. Dit leidt tot een veel voorkomende fout bij overbelasting bij het doorsturen van referentie:

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

Als er aan het eind geen enkele best haalbare kandidaat is, is de oproep dubbelzinnig:

void f(double ) { }
void f(float ) { }

f(42); // error: ambiguous

Rekenkundige promoties en conversies

Het is beter om een geheel getal naar het overeenkomstige gepromoveerde type te converteren dan naar een ander geheel getal.

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

Het is beter om een float te double dan deze te converteren naar een ander drijvend punttype.

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

Rekenkundige conversies anders dan promoties zijn niet beter of slechter dan elkaar.

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

Om er zeker van te zijn dat er geen dubbelzinnigheid is bij het aanroepen van een functie f met integrale of floating-point argumenten van elk standaardtype, zijn in totaal acht overbelastingen nodig, zodat voor elk mogelijk argumenttype een overbelasting overeenkomt exact of de unieke overbelasting met het gepromoveerde argumenttype wordt geselecteerd.

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);

Overbelasting binnen een klassenhiërarchie

In de volgende voorbeelden wordt deze klassenhiërarchie gebruikt:

struct A { int m; };
struct B : A {};
struct C : B {};

De conversie van afgeleid klasse-type naar basisklasse-type heeft de voorkeur boven door de gebruiker gedefinieerde conversies. Dit is van toepassing bij het doorgeven van waarde of referentie, evenals bij het converteren van aanwijzer naar afgeleid naar aanwijzer naar basis.

struct Unrelated {
    Unrelated(B b);
};
void f(A a);
void f(Unrelated u);
B b;
f(b); // calls f(A)

Een pointerconversie van afgeleide klasse naar basisklasse is ook beter dan conversie naar void* .

void f(A* p);
void f(void* p);
B b;
f(&b); // calls f(A*)

Als er meerdere overbelastingen binnen dezelfde overervingsketen zijn, heeft de meest afgeleide basisklasse overbelasting de voorkeur. Dit is gebaseerd op een soortgelijk principe als virtuele verzending: de "meest gespecialiseerde" implementatie wordt gekozen. Resolutie overbelasting treedt echter altijd op tijdens het compileren en wordt nooit impliciet down-cast.

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

Voor verwijzingen naar leden, die tegenstrijdig zijn met betrekking tot de klasse, geldt een vergelijkbare regel in de tegenovergestelde richting: de minst afgeleide afgeleide klasse heeft de voorkeur.

void f(int B::*p);
void f(int C::*p);
int A::*p = &A::m;
f(p); // calls f(int B::*)

Overbelasting van constness en volatiliteit

Het passeren van een pointer argument voor een T* parameter, indien mogelijk, is beter dan doorgeeft aan een 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

Evenzo is een argument doorgeven aan een T& -parameter, indien mogelijk, beter dan het doorgeven aan een const T& -parameter, zelfs als beide een exacte matchrang hebben.

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

Deze regel is ook van toepassing op const-gekwalificeerde lidfuncties, waar het belangrijk is om veranderlijke toegang toe te staan tot niet-const objecten en onveranderlijke toegang tot const objecten.

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

Op dezelfde manier zal een vluchtige overbelasting minder de voorkeur hebben dan een niet-vluchtige overbelasting.

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow