C++
Rozdzielczość przeciążenia
Szukaj…
Uwagi
Rozwiązanie problemu przeciążenia występuje w kilku różnych sytuacjach
- Wywołania nazwanych funkcji przeciążonych. Kandydaci to wszystkie funkcje znalezione według wyszukiwania nazw.
- Wywołania do obiektu klasy. Kandydaci to zwykle przeciążone operatory wywołania funkcji klasy.
- Korzystanie z operatora. Kandydatami są przeciążone funkcje operatora w zakresie przestrzeni nazw, przeciążone funkcje operatora w obiekcie lewej klasy (jeśli istnieje) i wbudowane operatory.
- Rozdzielczość przeciążenia, aby znaleźć poprawną funkcję operatora konwersji lub konstruktora do wywołania w celu inicjalizacji
- W przypadku bezpośredniej inicjalizacji innej niż lista (
Class c(value)
) kandydaci są konstruktoramiClass
. - Do inicjowania kopiowania poza listą (
Class c = value
) i do znalezienia funkcji konwersji zdefiniowanej przez użytkownika do wywołania w sekwencji konwersji zdefiniowanej przez użytkownika. Kandydaci są konstruktoramiClass
i jeśli źródłem jest obiekt klasy, jego operator konwersji działa. - Do inicjalizacji nieklasy z obiektu klasy (
Nonclass c = classObject
). Kandydaci są funkcjami operatora konwersji obiektu inicjalizującego. - Do inicjowania odwołania z obiektem klasy (
R &r = classObject
), gdy klasa ma funkcje operatora konwersji, które dają wartości, które można powiązać bezpośrednio zr
. Kandydaci są takimi funkcjami operatora konwersji. - Do inicjalizacji listy nieagregowanego obiektu klasy (
Class c{1, 2, 3}
) kandydaci są konstruktorami list inicjalizujących dla pierwszego przejścia przez rozwiązanie przeciążenia. Jeśli to nie znajdzie odpowiedniego kandydata, wykonywane jest drugie przejście przez rozwiązanie przeciążenia, z konstruktoramiClass
jako kandydatami.
- W przypadku bezpośredniej inicjalizacji innej niż lista (
Dokładne dopasowanie
Przeciążenie bez konwersji wymagane dla typów parametrów lub tylko konwersje między typami, które nadal są uważane za dokładne dopasowania, jest preferowane zamiast przeciążenia, które wymaga innych konwersji w celu wywołania.
void f(int x);
void f(double x);
f(42); // calls f(int)
Gdy argument wiąże się z odwołaniem do tego samego typu, uznaje się, że dopasowanie nie wymaga konwersji, nawet jeśli odwołanie ma większą kwalifikację 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
Do celów rozwiązania w przypadku przeciążenia uważa się, że typ „tablica T
” jest dokładnie dopasowany do typu „wskaźnik do T
”, a typ funkcji T
jest uważany za pasujący dokładnie do wskaźnika funkcji typu T*
, mimo że oba wymagają konwersje.
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
Kategoryzacja argumentu do kosztu parametru
Rozdzielczość przeciążenia dzieli koszt przekazania argumentu do parametru do jednej z czterech różnych kategorii, zwanych „sekwencjami”. Każda sekwencja może obejmować zero, jedną lub kilka konwersji
Standardowa sekwencja konwersji
void f(int a); f(42);
Zdefiniowana przez użytkownika sekwencja konwersji
void f(std::string s); f("hello");
Sekwencja konwersji elipsy
void f(...); f(42);
Lista sekwencji inicjalizacji
void f(std::vector<int> v); f({1, 2, 3});
Ogólna zasada jest taka, że standardowe sekwencje konwersji są najtańsze, po których następują zdefiniowane przez użytkownika sekwencje konwersji, a następnie sekwencje konwersji elipsy.
Szczególnym przypadkiem jest sekwencja inicjalizacji listy, która nie stanowi konwersji (lista inicjalizacyjna nie jest wyrażeniem typu). Jego koszt określa się, definiując go jako równoważny jednej z trzech pozostałych sekwencji konwersji, w zależności od typu parametru i formy listy inicjalizacyjnej.
Wyszukiwanie nazw i sprawdzanie dostępu
Rozwiązanie problemu przeciążenia występuje po wyszukiwaniu nazwy. Oznacza to, że funkcja lepszego dopasowania nie zostanie wybrana przez rozdzielczość przeciążenia, jeśli utraci wyszukiwanie nazwy:
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
};
Rozwiązanie problemu przeciążenia występuje przed sprawdzeniem dostępu. Niedostępną funkcję można wybrać na podstawie rozdzielczości przeciążenia, jeśli jest ona lepsza niż funkcja dostępna.
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.
Podobnie rozwiązuje się problem przeciążenia bez sprawdzania, czy wywołanie jest poprawnie sformułowane pod względem 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
Przeciążenie przy przekazywaniu Odniesienie
Należy zachować szczególną ostrożność podczas przekazywania przeciążenia referencyjnego przekazywania, ponieważ może ono zbyt dobrze pasować:
struct A {
A() = default; // #1
A(A const& ) = default; // #2
template <class T>
A(T&& ); // #3
};
Chodziło o to, aby A
można było skopiować, i że mamy tego innego konstruktora, który może zainicjować jakiegoś innego członka. Jednak:
A a; // calls #1
A b(a); // calls #3!
Istnieją dwa realne dopasowania dla wezwania do budowy:
A(A const& ); // #2
A(A& ); // #3, with T = A&
Oba są Dokładnymi Dopasowaniami, ale #3
odnosi się do obiektu mniej kwalifikowanego pod względem cv niż #2
, więc ma lepszą standardową sekwencję konwersji i jest najlepszą możliwą do wykonania funkcją.
Rozwiązaniem tutaj jest zawsze ograniczenie tych konstruktorów (np. Przy użyciu SFINAE):
template <class T,
class = std::enable_if_t<!std::is_convertible<std::decay_t<T>*, A*>::value>
>
A(T&& );
Cechą typu jest tutaj wykluczenie jakiejkolwiek klasy A
lub klasy, publicznie i jednoznacznie wyprowadzonej z A
, co spowodowałoby, że ten konstrukt byłby źle sformułowany w przykładzie opisanym wcześniej (a zatem usunięty z zestawu przeciążenia). W rezultacie wywoływany jest konstruktor kopii - tego właśnie chcieliśmy.
Kroki rozwiązania problemu przeciążenia
Kroki rozwiązania problemu przeciążenia to:
Znajdź funkcje kandydatów poprzez wyszukiwanie nazw. Wywołania niewykwalifikowane będą przeprowadzać zarówno zwykłe wyszukiwanie niekwalifikowane, jak i wyszukiwanie zależne od argumentów (jeśli dotyczy).
Filtruj zestaw funkcji kandydujących do zestawu funkcji wykonalnych . Funkcjonalna funkcja, dla której istnieje niejawna sekwencja konwersji między argumentami wywoływanymi przez funkcję a parametrami, które przyjmuje funkcja.
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
Wybierz najlepszego wykonawcę. Funkcjonalna funkcja
F1
jest lepszą funkcją niż inna funkcjonalna funkcjaF2
jeśli niejawna sekwencja konwersji dla każdego argumentu wF1
nie jest gorsza niż odpowiednia niejawna sekwencja konwersji wF2
i ...:3.1 W przypadku niektórych argumentów niejawna sekwencja konwersji dla tego argumentu w
F1
jest lepszą sekwencją konwersji niż dla tego argumentu wF2
lubvoid f(int ); // (1) void f(char ); // (2) f(4); // call (1), better conversion sequence
3.2 W przypadku konwersji zdefiniowanej przez użytkownika standardowa sekwencja konwersji od powrotu
F1
do typu docelowego jest lepszą sekwencją konwersji niż typ zwracanyF2
lubstruct 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 W bezpośrednim wiązaniu odniesienia
F1
ma ten sam rodzaj odniesienia, ponieważF2
nie, lubstruct A { operator X&(); // #1 operator X&&(); // #2 }; A a; X& lx = a; // calls #1 X&& rx = a; // calls #2
3.4
F1
nie jest specjalizacją szablonu funkcji, aleF2
to, lubtemplate <class T> void f(T ); // #1 void f(int ); // #2 f(42); // calls #2, the non-template
3.5
F1
iF2
to specjalizacje szablonów funkcji, aleF1
jest bardziej wyspecjalizowana niżF2
.template <class T> void f(T ); // #1 template <class T> void f(T* ); // #2 int* p; f(p); // calls #2, more specialized
Zamawianie tutaj jest znaczące. Lepsze sprawdzanie sekwencji konwersji następuje przed sprawdzeniem szablonu w porównaniu do sprawdzenia innego niż szablon. Prowadzi to do typowego błędu związanego z przeciążeniem przy przekazywaniu odwołania:
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
Jeśli na końcu nie ma jednego najlepszego kandydata, połączenie jest niejednoznaczne:
void f(double ) { }
void f(float ) { }
f(42); // error: ambiguous
Promocje arytmetyczne i konwersje
Konwersja typu liczb całkowitych na odpowiadający mu typ promowany jest lepsza niż konwersja na inny typ liczb całkowitych.
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
Promowanie float
do double
jest lepsza niż przekształcenie go do jakiegoś innego pływającego typu punktowego.
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
Konwersje arytmetyczne inne niż promocje nie są ani lepsze, ani gorsze od siebie.
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
Dlatego w celu zapewnienia, że nie będzie dwuznaczności podczas wywoływania funkcji f
z argumentami integralnymi lub zmiennoprzecinkowymi dowolnego typu standardowego, potrzeba łącznie ośmiu przeciążeń, tak aby dla każdego możliwego typu argumentu przeciążenie pasowało dokładnie lub zostanie wybrane unikalne przeciążenie promowanym typem argumentu.
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);
Przeciążenie w hierarchii klas
Poniższe przykłady wykorzystają tę hierarchię klas:
struct A { int m; };
struct B : A {};
struct C : B {};
Konwersja z pochodnego typu klasy na podstawowy typ klasy jest lepsza niż konwersje zdefiniowane przez użytkownika. Dotyczy to przekazywania wartości lub referencji, a także konwertowania wskaźnika na pochodną na wskaźnik na bazę.
struct Unrelated {
Unrelated(B b);
};
void f(A a);
void f(Unrelated u);
B b;
f(b); // calls f(A)
Konwersja wskaźnika z klasy pochodnej na klasę podstawową jest również lepsza niż konwersja na void*
.
void f(A* p);
void f(void* p);
B b;
f(&b); // calls f(A*)
Jeśli występuje wiele przeciążeń w tym samym łańcuchu dziedziczenia, preferowane jest najbardziej wyprowadzone przeciążenie klasy podstawowej. Opiera się to na zasadzie podobnej do wirtualnej wysyłki: wybierana jest „najbardziej wyspecjalizowana” implementacja. Jednak rozwiązanie przeciążenia zawsze występuje w czasie kompilacji i nigdy nie będzie pośrednio obniżane.
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
W przypadku wskaźników do elementów, które są sprzeczne w stosunku do klasy, stosuje się podobną zasadę w przeciwnym kierunku: preferowana jest najmniej pochodna klasa pochodna.
void f(int B::*p);
void f(int C::*p);
int A::*p = &A::m;
f(p); // calls f(int B::*)
Przeciążenie stałości i zmienności
Przekazywanie argumentu wskaźnika do parametru T*
, jeśli to możliwe, jest lepsze niż przekazywanie go do parametru 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
Podobnie, przekazanie argumentu do parametru T&
parametru, jeśli to możliwe, jest lepsze niż przekazanie go do parametru const T&
parametru, nawet jeśli oba mają dokładną rangę dopasowania.
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
Zasada ta dotyczy również funkcji składowych const, gdzie jest ważna dla umożliwienia mutowalnego dostępu do obiektów non-const i niezmiennego dostępu do obiektów 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
W ten sam sposób, lotne przeciążenie będzie mniej korzystne niż nielotne przeciążenie.
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