C++
Risoluzione di sovraccarico
Ricerca…
Osservazioni
La risoluzione del sovraccarico si verifica in diverse situazioni
- Chiamate a funzioni con sovraccarico con nome. I candidati sono tutte le funzioni trovate per la ricerca del nome.
- Chiama oggetto di classe. I candidati sono di solito tutti gli operatori di chiamata di funzione sovraccaricati della classe.
- Uso di un operatore. I candidati sono le funzioni dell'operatore sovraccarico nello scope dei nomi, le funzioni dell'operatore sovraccarico nell'oggetto di classe sinistro (se presente) e gli operatori integrati.
- Risoluzione di sovraccarico per trovare la funzione o il costruttore di conversione corretta da richiamare per un'inizializzazione
- Per l'inizializzazione diretta non di elenco (
Class c(value)
), i candidati sono costruttori diClass
. - Per l'inizializzazione della copia non di elenco (
Class c = value
) e per trovare la funzione di conversione definita dall'utente da richiamare in una sequenza di conversione definita dall'utente. I candidati sono i costruttori diClass
e se l'origine è un oggetto di classe, le sue funzioni di operatore di conversione. - Per l'inizializzazione di una non classe da un oggetto di classe (
Nonclass c = classObject
). I candidati sono le funzioni dell'operatore di conversione dell'oggetto inizializzatore. - Per inizializzare un riferimento con un oggetto di classe (
R &r = classObject
), quando la classe ha funzioni di operatore di conversione che producono valori che possono essere associati direttamente ar
. I candidati sono tali funzioni di operatore di conversione. - Per l'inizializzazione di una lista di un oggetto di classe non aggregato (
Class c{1, 2, 3}
), i candidati sono i costruttori dell'elenco di inizializzatori per una prima passata attraverso la risoluzione di sovraccarico. Se questo non trova un candidato valido, viene eseguito un secondo passaggio attraverso la risoluzione di sovraccarico, con i costruttori diClass
come candidati.
- Per l'inizializzazione diretta non di elenco (
Corrispondenza esatta
Un sovraccarico senza conversioni necessario per i tipi di parametri o solo le conversioni necessarie tra tipi che sono ancora considerati corrispondenze esatte è preferito su un sovraccarico che richiede altre conversioni per chiamare.
void f(int x);
void f(double x);
f(42); // calls f(int)
Quando un argomento si lega a un riferimento allo stesso tipo, si considera che la corrispondenza non richieda una conversione anche se il riferimento è più qualificato cv.
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
Ai fini della risoluzione di sovraccarico, il tipo "array di T
" viene considerato corrispondente esattamente al tipo "puntatore a T
" e il tipo di funzione T
viene considerato corrispondente esattamente al puntatore di funzione di tipo T*
, anche se entrambi richiedono conversioni.
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
Categorizzazione dell'argomento al costo del parametro
La risoluzione di sovraccarico divide il costo del passaggio di un argomento a un parametro in una di quattro diverse categorie, chiamate "sequenze". Ogni sequenza può includere zero, una o più conversioni
Sequenza di conversione standard
void f(int a); f(42);
Sequenza di conversione definita dall'utente
void f(std::string s); f("hello");
Sequenza di conversione di ellissi
void f(...); f(42);
Elenco sequenza di inizializzazione
void f(std::vector<int> v); f({1, 2, 3});
Il principio generale è che le sequenze di conversione standard sono le più economiche, seguite da sequenze di conversione definite dall'utente, seguite da sequenze di conversione ellissi.
Un caso speciale è la sequenza di inizializzazione dell'elenco, che non costituisce una conversione (una lista di inizializzazione non è un'espressione con un tipo). Il suo costo è determinato definendolo come equivalente a una delle altre tre sequenze di conversione, a seconda del tipo di parametro e della forma dell'elenco di inizializzazione.
Ricerca dei nomi e controllo degli accessi
La risoluzione del sovraccarico si verifica dopo la ricerca del nome. Ciò significa che una funzione di corrispondenza migliore non verrà selezionata dalla risoluzione di sovraccarico se perde la ricerca del nome:
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
};
La risoluzione del sovraccarico si verifica prima del controllo dell'accesso. Una funzione inaccessibile potrebbe essere selezionata dalla risoluzione di sovraccarico se è una corrispondenza migliore di una funzione accessibile.
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.
Allo stesso modo, la risoluzione del sovraccarico si verifica senza verificare se la chiamata risultante è ben formata per quanto riguarda l' 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
Sovraccarico sul riferimento di inoltro
È necessario prestare molta attenzione quando si fornisce un sovraccarico di riferimento di inoltro poiché potrebbe corrispondere troppo bene:
struct A {
A() = default; // #1
A(A const& ) = default; // #2
template <class T>
A(T&& ); // #3
};
L'intento qui era che A
è copiabile e che abbiamo questo altro costruttore che potrebbe inizializzare qualche altro membro. Però:
A a; // calls #1
A b(a); // calls #3!
Ci sono due partite valide per la chiamata di costruzione:
A(A const& ); // #2
A(A& ); // #3, with T = A&
Entrambe sono corrispondenze esatte, ma #3
prende un riferimento a un oggetto con meno cv rispetto a #2
, quindi ha la migliore sequenza di conversione standard ed è la migliore funzione possibile.
La soluzione qui è di limitare sempre questi costruttori (ad es. Usando SFINAE):
template <class T,
class = std::enable_if_t<!std::is_convertible<std::decay_t<T>*, A*>::value>
>
A(T&& );
Il carattere type qui è quello di escludere qualsiasi A
o classe derivata pubblicamente e senza ambiguità da A
, che renderebbe questo costruttore malformato nell'esempio descritto in precedenza (e quindi rimosso dal set di overload). Di conseguenza, viene invocato il costruttore di copia, che è ciò che volevamo.
Passi per la risoluzione del sovraccarico
I passaggi della risoluzione di sovraccarico sono:
Trova le funzioni candidate tramite la ricerca del nome. Le chiamate non qualificate eseguiranno sia una ricerca regolare non regolare che una ricerca dipendente dall'argomento (se applicabile).
Filtra l'insieme di funzioni candidate in un insieme di funzioni vitali . Una funzione valida per la quale esiste una sequenza di conversione implicita tra gli argomenti con cui la funzione viene chiamata e i parametri che la funzione assume.
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
Scegli il miglior candidato possibile. Una funzione valida
F1
è una funzione migliore di un'altra funzione validaF2
se la sequenza di conversione implicita per ciascun argomento inF1
non è peggiore della corrispondente sequenza di conversione implicita inF2
, e ...:3.1. Per alcuni argomenti, la sequenza di conversione implicita per quell'argomento in
F1
è una sequenza di conversione migliore rispetto a quell'argomento inF2
, ovoid f(int ); // (1) void f(char ); // (2) f(4); // call (1), better conversion sequence
3.2. In una conversione definita dall'utente, la sequenza di conversione standard dal ritorno di
F1
al tipo di destinazione è una sequenza di conversione migliore rispetto a quella del tipo di ritorno diF2
, ostruct 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 un legame di riferimento diretto,
F1
ha lo stesso tipo di riferimento diF2
non lo è, ostruct A { operator X&(); // #1 operator X&&(); // #2 }; A a; X& lx = a; // calls #1 X&& rx = a; // calls #2
3.4.
F1
non è una specializzazione del modello di funzione, maF2
è, otemplate <class T> void f(T ); // #1 void f(int ); // #2 f(42); // calls #2, the non-template
3.5.
F1
eF2
sono entrambe specializzazioni del modello di funzione, maF1
è più specializzato diF2
.template <class T> void f(T ); // #1 template <class T> void f(T* ); // #2 int* p; f(p); // calls #2, more specialized
L'ordine qui è significativo. Il controllo della sequenza di conversione migliore avviene prima del controllo modello vs non modello. Ciò porta a un errore comune con sovraccarico sul riferimento di inoltro:
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
Se alla fine non c'è un singolo candidato valido, la chiamata è ambigua:
void f(double ) { }
void f(float ) { }
f(42); // error: ambiguous
Promozioni aritmetiche e conversioni
Convertire un tipo intero nel tipo promosso corrispondente è meglio che convertirlo in un altro tipo intero.
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
Promuovere un float
da double
è meglio che convertirlo in un altro tipo di floating point.
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
Le conversioni aritmetiche diverse dalle promozioni non sono né migliori né peggiori delle altre.
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
Pertanto, al fine di garantire che non ci sia ambiguità quando si chiama una funzione f
con argomenti interi o in virgola mobile di qualsiasi tipo standard, sono necessari un totale di otto overload, in modo che per ogni tipo di argomento possibile sia una corrispondenza di sovraccarico esattamente o verrà selezionato l'overload univoco con il tipo di argomento promosso.
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);
Sovraccarico all'interno di una gerarchia di classi
I seguenti esempi useranno questa gerarchia di classi:
struct A { int m; };
struct B : A {};
struct C : B {};
La conversione dal tipo di classe derivata al tipo di classe base è preferibile alle conversioni definite dall'utente. Questo si applica quando si passa per valore o per riferimento, così come quando si converte il puntatore-derivato in puntatore-base.
struct Unrelated {
Unrelated(B b);
};
void f(A a);
void f(Unrelated u);
B b;
f(b); // calls f(A)
Anche una conversione puntatore dalla classe derivata alla classe base è migliore della conversione in void*
.
void f(A* p);
void f(void* p);
B b;
f(&b); // calls f(A*)
Se ci sono sovraccarichi multipli all'interno della stessa catena di ereditarietà, viene preferito l'overload di classe base più derivato. Questo si basa su un principio simile a quello di dispacciamento virtuale: viene scelta l'implementazione "più specializzata". Tuttavia, la risoluzione del sovraccarico si verifica sempre al momento della compilazione e non verrà mai implicitamente abbassata.
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
Per i riferimenti ai membri, che sono controvarianti rispetto alla classe, una regola simile si applica nella direzione opposta: la classe derivata meno derivata è preferita.
void f(int B::*p);
void f(int C::*p);
int A::*p = &A::m;
f(p); // calls f(int B::*)
Sovraccarico di costanza e volatilità
Passare un argomento puntatore a un parametro T*
, se possibile, è meglio che passarlo a un parametro const T*
.
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
Allo stesso modo, passare un argomento a un parametro T&
, se possibile, è meglio che passarlo a un parametro const T&
, anche se entrambi hanno una corrispondenza esatta.
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
Questa regola si applica anche alle funzioni membro const-qualificato, dove è importante consentire l'accesso mutevole agli oggetti non-const e l'accesso immutabile agli oggetti const.
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
Allo stesso modo, un sovraccarico volatile sarà meno preferito rispetto a un sovraccarico non volatile.
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