C++
Överbelastningsupplösning
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 avClass
. - 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 ärClass
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 tillr
. 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 avClass
som kandidater.
- För direktinitiering utan listan (
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:
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).
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
Välj den bästa möjliga kandidaten. En livskraftig funktion
F1
är en bättre funktion än en annan livskraftig funktionF2
om den implicita konverteringssekvensen för varje argument iF1
inte är sämre än motsvarande implicita konverteringssekvens iF2
, 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 iF2
, ellervoid 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örF2
, ellerstruct 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 medF2
inte, ellerstruct 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, menF2
är ellertemplate <class T> void f(T ); // #1 void f(int ); // #2 f(42); // calls #2, the non-template
3,5.
F1
ochF2
är båda specialiserade funktionsmallar, menF1
är mer specialiserade änF2
.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