W poprzedniej lekcji (11.1 -- Wprowadzenie do funkcji przeciążenie), wprowadziliśmy koncepcję przeciążenia funkcji, która pozwala nam tworzyć wiele funkcji o tej samej nazwie, pod warunkiem, że każda funkcja o identycznej nazwie ma inny typ parametrów (lub funkcje mogą być inaczej zróżnicowane).
W tej lekcji przyjrzymy się bliżej, jak różniczkowane są przeciążone funkcje. Przeciążone funkcje, które nie są odpowiednio zróżnicowane, spowodują, że kompilator wygeneruje błąd kompilacji.
Jak różnicowane są przeciążone funkcje
| Właściwość funkcji | Używana do różnicowania | Uwagi |
|---|---|---|
| Liczba parametrów | Tak | |
| Typ parametrów | Tak | Wyklucza typedefs, aliasy typów i kwalifikator const na wartości parametry. Zawiera elipsy. |
| Typ zwracany | Nie |
Zauważ, że typ zwracany przez funkcję nie jest używany do rozróżniania funkcji przeciążonych. Omówimy to szerzej za chwilę.
Dla zaawansowanych czytelników
W przypadku funkcji składowych uwzględniane są również dodatkowe kwalifikatory poziomu funkcji:
| Kwalifikator poziomu funkcji | Używany do przeciążenia |
|---|---|
| const lub volatile | Tak |
| Kwalifikatory ref | Tak |
Na przykład funkcja składowa const może można odróżnić od identycznej funkcji składowej niebędącej stałą (nawet jeśli mają ten sam zestaw parametrów).
Powiązana treść
Elipsę omówimy w lekcji 20.5 -- Elipsa (i dlaczego jej unikać).
Przeciążenie w oparciu o liczbę parametrów
Przeciążona funkcja jest różnicowana pod warunkiem, że każda przeciążona funkcja ma inną liczbę parametrów. Na przykład:
int add(int x, int y)
{
return x + y;
}
int add(int x, int y, int z)
{
return x + y + z;
}Kompilator może łatwo stwierdzić, że wywołanie funkcji z dwoma parametrami całkowitymi powinno zostać skierowane do add(int, int) a wywołanie funkcji z trzema parametrami całkowitymi powinno zostać skierowane do add(int, int, int).
Przeciążenie w oparciu o typ parametrów
Funkcję można również różnicować, o ile lista typów parametrów każdej przeciążonej funkcji jest odrębna. Na przykład rozróżniane są wszystkie następujące przeciążenia:
int add(int x, int y); // integer version
double add(double x, double y); // floating point version
double add(int x, double y); // mixed version
double add(double x, int y); // mixed versionPonieważ aliasy typów (lub typedefs) nie są odrębnymi typami, przeciążone funkcje korzystające z aliasów typów nie różnią się od przeciążeń korzystających z typu aliasu. Na przykład wszystkie poniższe przeciążenia nie są różnicowane (i spowodują błąd kompilacji):
typedef int Height; // typedef
using Age = int; // type alias
void print(int value);
void print(Age value); // not differentiated from print(int)
void print(Height value); // not differentiated from print(int)W przypadku parametrów przekazywanych przez wartość kwalifikator const również nie jest brany pod uwagę. Dlatego też następujące funkcje nie są uważane za zróżnicowane:
void print(int);
void print(const int); // not differentiated from print(int)Dla zaawansowanych czytelników
Nie omówiliśmy jeszcze wielokropka, ale parametry elipsy są uważane za unikalny typ parametru:
void foo(int x, int y);
void foo(int x, ...); // differentiated from foo(int, int)Zatem wywołanie foo(4, 5) będzie pasować do foo(int, int), nie foo(int, ...).
Typ zwracany przez funkcję nie jest brany pod uwagę przy różnicowaniu
Typ zwracany funkcji nie jest brany pod uwagę, gdy różnicowanie przeciążonych funkcji.
Rozważmy przypadek, w którym chcesz napisać funkcję zwracającą liczbę losową, ale potrzebujesz wersji, która zwróci liczbę int, i innej wersji, która zwróci wartość double. Możesz ulec pokusie, aby to zrobić:
int getRandomValue();
double getRandomValue();W programie Visual Studio 2019 powoduje to następujący błąd kompilatora:
error C2556: 'double getRandomValue(void)': overloaded function differs only by return type from 'int getRandomValue(void)'
To ma sens. Gdybyś był kompilatorem i zobaczył tę instrukcję:
getRandomValue();Którą z dwóch przeciążonych funkcji byś wywołał? Nie jest to jasne.
Na marginesie…
Był to celowy wybór, ponieważ zapewnia, że zachowanie wywołania funkcji można określić niezależnie od reszty wyrażenia, co znacznie ułatwia zrozumienie złożonych wyrażeń. Innymi słowy, zawsze możemy określić, która wersja funkcji zostanie wywołana, wyłącznie na podstawie argumentów w wywołaniu funkcji. Jeśli do różnicowania użyto zwracanych wartości, nie mielibyśmy łatwego syntaktycznego sposobu na stwierdzenie, które przeciążenie funkcji zostało wywołane — musielibyśmy także zrozumieć, w jaki sposób wartość zwracana została użyta, co wymaga znacznie większej analizy.
Najlepszym sposobem rozwiązania tego problemu jest nadanie funkcjom różnych nazw:
int getRandomInt();
double getRandomDouble();Podpis typu
Typ funkcji podpis (ogólnie nazywany podpisem) definiuje się jako części nagłówka funkcji, które służą do różnicowania funkcji. W C++ obejmuje to nazwę funkcji, liczbę parametrów, typ parametru i kwalifikatory na poziomie funkcji. W szczególności nie obejmuje typ zwracany.
Zniekształcanie nazw
Na marginesie…
Kiedy kompilator kompiluje funkcję, wykonuje zmienianie nazw, co oznacza, że skompilowana nazwa funkcji jest zmieniana („zniekształcana”) w oparciu o różne kryteria, takie jak liczba i typ parametrów, dzięki czemu linker ma unikalne nazwy do działania with.
Na przykład funkcja z prototypem int fcn() może zostać skompilowana do zniekształconej nazwy __fcn_v, podczas gdy int fcn(int) może zostać skompilowana do zniekształconej nazwy __fcn_i. Zatem podczas gdy w kodzie źródłowym dwie przeciążone funkcje mają tę samą nazwę fcn(), w skompilowanym kodzie zniekształcone nazwy są unikalne (__fcn_v vs __fcn_i).
Nie ma standaryzacji dotyczącej sposobu zniekształcania nazw, więc różne kompilatory będą generować różne zniekształcone nazwy.

