Sök…


Anmärkningar

Överbelastningsupplösning sker i flera olika situationer

  • Samtal till namngivna överbelastade funktioner. Kandidaterna är alla funktioner som hittas vid namnuppslag.
  • Samtal till klassobjekt. Kandidaterna är vanligtvis alla överbelastade funktionssamtaloperatörer i klassen.
  • Användning av en operatör. Kandidaterna är de överbelastade operatörsfunktionerna i namnområdet, de överbelastade operatörsfunktionerna i det vänstra klassobjektet (om sådana finns) och de inbyggda operatörerna.
  • Överbelastningsupplösning för att hitta rätt konverteringsoperatörsfunktion eller konstruktör att åberopa för en initialisering
    • För direktinitiering utan listan ( Class c(value) ) är kandidaterna konstruktörer av Class .
    • För initiering av icke-listkopiering ( Class c = value ) och för att hitta den användardefinierade konverteringsfunktionen som ska anropas i en användardefinierad konverteringssekvens. Kandidaterna är Class konstruktörer och om källan är ett klassobjekt fungerar dess konverteringsoperatör.
    • För initialisering av en icke-klass från ett Nonclass c = classObject ( Nonclass c = classObject ). Kandidaterna är konverteringsoperatörens funktioner för initialiseringsobjektet.
    • För att initialisera en referens med ett klassobjekt ( R &r = classObject ), när klassen har konverteringsoperatörsfunktioner som ger värden som kan bindas direkt till r . Kandidaterna är sådana konverteringsoperatörsfunktioner.
    • För listinitialisering av ett icke-aggregerat klassobjekt ( Class c{1, 2, 3} ) är kandidaterna initialistlistarkonstruktörer för en första passering genom överbelastningsupplösning. Om detta inte hittar en livskraftig kandidat görs en andra genomgångsbelastningsupplösning, med konstruktörerna av Class som kandidater.

Exakt matchning

En överbelastning utan konverteringar som behövs för parametertyper eller endast konverteringar som behövs mellan typer som fortfarande anses vara exakta matchningar föredras framför en överbelastning som kräver andra konverteringar för att ringa.

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

När ett argument binder till en referens till samma typ anses matchningen inte kräva en konvertering även om referensen är mer cv-kvalificerad.

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

För överbelastningsupplösning anses typen "array of T " matcha exakt med typen "pekare till T ", och funktionstypen T anses matcha exakt med funktionen pekare typ T* , även om båda kräver omvandlingar.

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

Kategorisering av argument till parameterkostnad

Överbelastningsupplösning partitionerar kostnaden för att överföra ett argument till en parameter i en av fyra olika kategorier, kallade "sekvenser". Varje sekvens kan inkludera noll, en eller flera omvandlingar

  • Standardomvandlingssekvens

    void f(int a); f(42);
    
  • Användardefinierad konverteringssekvens

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

    void f(...); f(42);
    
  • Lista initialiseringssekvens

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

Den allmänna principen är att standardomvandlingssekvenser är de billigaste följt av användardefinierade omvandlingssekvenser följt av ellipsomvandlingssekvenser.

Ett specialfall är listinitialiseringssekvensen, som inte utgör en konvertering (en initialiseringslista är inte ett uttryck med en typ). Kostnaden bestäms genom att definiera den till att vara ekvivalent med en av de andra tre konverteringssekvenserna, beroende på parametertyp och form på initialiseringslistan.

Namnsökning och åtkomstkontroll

Överbelastningsupplösning inträffar efter namnsökning. Detta innebär att en bättre matchande funktion inte väljs med överbelastningsupplösning om den förlorar namnuppslag:

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

Överbelastningsupplösning sker före åtkomstkontroll. En otillgänglig funktion kan väljas med överbelastningsupplösning om det är en bättre matchning än en tillgänglig funktion.

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.

På liknande sätt sker överbelastningsupplösning utan att kontrollera om det resulterande samtalet är välformat med avseende på 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

Överbelastning vid vidarebefordran

Du måste vara mycket försiktig när du tillhandahåller en överbelastning för vidarebefordran, eftersom det kan matcha för bra:

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

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

Avsikten här var att A är kopierbar, och att vi har denna andra konstruktör som kan initialisera någon annan medlem. Dock:

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

Det finns två genomförbara matchningar för byggsamtalet:

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

Båda är exakta matchningar, men #3 hänvisar till ett mindre cv- kvalificerat objekt än #2 gör, så det har den bättre konverteringssekvensen som är bättre och är den bästa genomförbara funktionen.

Lösningen här är att alltid begränsa dessa konstruktörer (t.ex. med SFINAE):

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

Typegenskapen här är att utesluta alla A eller klasser som offentligt och otvetydigt härrör från A från övervägande, vilket skulle göra denna konstruktör dåligt formad i exemplet som beskrivits tidigare (och därmed tas bort från överbelastningsuppsättningen). Som ett resultat åberopas kopikonstruktören - vilket vi ville ha.

Steg för överbelastningsupplösning

Stegen för överbelastningsupplösning är:

  1. Hitta kandidatfunktioner via namnsökning. Okvalificerade samtal kommer att utföra både regelbunden okvalificerad sökning såväl som argumentberoende uppslagning (om tillämpligt).

  2. Filtrera uppsättningen kandidatfunktioner till en uppsättning livskraftiga funktioner. En livskraftig funktion för vilken det finns en implicit omvandlingssekvens mellan de argument som funktionen kallas med och parametrarna som funktionen tar.

    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. Välj den bästa möjliga kandidaten. En livskraftig funktion F1 är en bättre funktion än en annan livskraftig funktion F2 om den implicita konverteringssekvensen för varje argument i F1 inte är sämre än motsvarande implicita konverteringssekvens i F2 , och ...:

    3,1. För vissa argument är den implicita konverteringssekvensen för det argumentet i F1 en bättre konverteringssekvens än för det argumentet i F2 , eller

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

    3,2. I en användardefinierad konvertering är standardomvandlingssekvensen från returen från F1 till destinationstypen en bättre konverteringssekvens än den för returtypen för F2 , eller

    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. I en direkt referensbindning har F1 samma typ av referens med F2 inte, eller

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

    3,4. F1 är inte en specialisering av funktionsmallar, men F2 är eller

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

    3,5. F1 och F2 är båda specialiserade funktionsmallar, men F1 är mer specialiserade än F2 .

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

Beställningen här är betydelsefull. Den bättre konverteringssekvenskontrollen sker före mallen kontra icke-mallen kontroll. Detta leder till ett vanligt fel med överbelastning vid vidarebefordran:

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

Om det inte finns någon enda bästa genomförbara kandidat i slutet är samtalet tvetydigt:

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

f(42); // error: ambiguous

Aritmetiska kampanjer och omvandlingar

Att konvertera en heltalstyp till motsvarande promoterad typ är bättre än att konvertera den till någon annan heltalstyp.

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

Att marknadsföra en float till double är bättre än att konvertera den till någon annan flytande punkttyp.

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

Andra aritmetiska omvandlingar än kampanjer är varken bättre eller sämre än varandra.

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

För att säkerställa att det inte finns någon tvetydighet när man kallar en funktion f med antingen integrerade eller flytande punktsargument av någon standardtyp behövs totalt åtta överbelastningar, så att för varje möjlig argumenttyp matchar antingen en överbelastning exakt eller den unika överbelastningen med den promoterade argumenttypen kommer att väljas.

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

Överbelastning inom en klasshierarki

Följande exempel använder denna klasshierarki:

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

Konverteringen från härledd klasstyp till basklasstyp föredras framför användardefinierade konverteringar. Detta gäller vid övergång till värde eller genom referens, liksom vid konvertering av pekare till härledd till pekare till bas.

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

En pekarkonvertering från härledd klass till basklass är också bättre än omvandling till void* .

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

Om det finns flera överbelastningar inom samma arvkedja föredras den mest härledda överklassens överbelastning. Detta är baserat på en liknande princip som virtuell avsändning: den "mest specialiserade" implementeringen väljs. Emellertid sker överbelastningsupplösning alltid vid sammanställningstiden och kommer aldrig implicit att kastas ner.

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 pekare till medlemmar, som strider mot klassen, gäller en liknande regel i motsatt riktning: den minst härledda klassen föredras.

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

Överbelastning på konstness och flyktighet

Att överföra ett pekarargument till en T* -parameter, om möjligt, är bättre än att skicka det till en 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

Likaså är det möjligt att överföra ett argument till en T& -parameter, om möjligt, att överföra det till en const T& -parameter, även om båda har exakt matchningsrankning.

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

Denna regel gäller också för const-kvalificerade medlemsfunktioner, där det är viktigt för att möjliggöra förändrad åtkomst till icke-const-objekt och immutable åtkomst till const-objekt.

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

På samma sätt kommer en flyktig överbelastning att föredras mindre än en icke-flyktig överbelastning.

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow