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ą konstruktorami Class .
    • 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ą konstruktorami Class 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 z r . 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 konstruktorami Class jako kandydatami.

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:

  1. 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).

  2. 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
    
  3. Wybierz najlepszego wykonawcę. Funkcjonalna funkcja F1 jest lepszą funkcją niż inna funkcjonalna funkcja F2 jeśli niejawna sekwencja konwersji dla każdego argumentu w F1 nie jest gorsza niż odpowiednia niejawna sekwencja konwersji w F2 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 w F2 lub

    void 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 zwracany F2 lub

    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 W bezpośrednim wiązaniu odniesienia F1 ma ten sam rodzaj odniesienia, ponieważ F2 nie, lub

    struct 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, ale F2 to, lub

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

    3.5 F1 i F2 to specjalizacje szablonów funkcji, ale F1 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


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow