C++
Przeciążenie funkcji
Szukaj…
Wprowadzenie
Zobacz także osobny temat dotyczący rozwiązywania problemu przeciążenia
Uwagi
Niejednoznaczności mogą wystąpić, gdy jeden typ może być niejawnie przekształcony w więcej niż jeden typ i nie ma funkcji dopasowania dla tego konkretnego typu.
Na przykład:
void foo(double, double);
void foo(long, long);
//Call foo with 2 ints
foo(1, 2); //Function call is ambiguous - int can be converted into a double/long at the same time
Co to jest przeciążenie funkcji?
Przeciążenie funkcji polega na tym, że wiele funkcji zadeklarowanych w tym samym zakresie, o dokładnie takiej samej nazwie, istnieje w tym samym miejscu (znanym jako zakres ), różniących się tylko podpisem , co oznacza argumenty, które akceptują.
Załóżmy, że piszesz szereg funkcji dla ogólnych możliwości drukowania, zaczynając od std::string
:
void print(const std::string &str)
{
std::cout << "This is a string: " << str << std::endl;
}
Działa to dobrze, ale załóżmy, że potrzebujesz funkcji, która akceptuje również int
i drukuje to również. Możesz napisać:
void print_int(int num)
{
std::cout << "This is an int: " << num << std::endl;
}
Ale ponieważ dwie funkcje akceptują różne parametry, możesz po prostu napisać:
void print(int num)
{
std::cout << "This is an int: " << num << std::endl;
}
Teraz masz 2 funkcje, obie o nazwie print
, ale z różnymi podpisami. Jeden akceptuje std::string
, drugi int
. Teraz możesz do nich dzwonić, nie martwiąc się o różne nazwiska:
print("Hello world!"); //prints "This is a string: Hello world!"
print(1337); //prints "This is an int: 1337"
Zamiast:
print("Hello world!");
print_int(1337);
Po przeciążeniu funkcji kompilator ustala, które funkcje wywołać z podanych parametrów. Należy zachować ostrożność podczas pisania przeciążenia funkcji. Na przykład z konwersjami typu niejawnego:
void print(int num)
{
std::cout << "This is an int: " << num << std::endl;
}
void print(double num)
{
std::cout << "This is a double: " << num << std::endl;
}
Teraz nie jest od razu jasne, które przeciążenie print
wywołuje się podczas pisania:
print(5);
Może być konieczne podanie kompilatorowi wskazówek, takich jak:
print(static_cast<double>(5));
print(static_cast<int>(5));
print(5.0);
Należy również zachować ostrożność podczas pisania przeciążeń, które akceptują parametry opcjonalne:
// WRONG CODE
void print(int num1, int num2 = 0) //num2 defaults to 0 if not included
{
std::cout << "These are ints: << num1 << " and " << num2 << std::endl;
}
void print(int num)
{
std::cout << "This is an int: " << num << std::endl;
}
Ponieważ kompilator nie może stwierdzić, czy wywołanie typu print(17)
jest przeznaczone dla pierwszej czy drugiej funkcji z powodu opcjonalnego drugiego parametru, kompilacja nie powiedzie się.
Typ powrotu w funkcji Przeciążenie
Pamiętaj, że nie można przeciążać funkcji na podstawie jej typu zwracanego. Na przykład:
// WRONG CODE
std::string getValue()
{
return "hello";
}
int getValue()
{
return 0;
}
int x = getValue();
Spowoduje to błąd kompilacji, ponieważ kompilator nie będzie w stanie ustalić, która wersja getValue
zostać wywołana, nawet jeśli typ zwracany jest przypisany do int
.
Funkcja członka Kwalifikator cv Przeciążenie
Funkcje w klasie mogą być przeciążone, gdy są dostępne poprzez odwołanie do tej klasy kwalifikowane cv; jest to najczęściej używane do przeciążania dla const
, ale może być również używane do przeciążania dla volatile
i const volatile
. Jest tak, ponieważ wszystkie niestatyczne funkcje składowe traktują this
jako parametr ukryty, do którego są stosowane kwalifikatory cv. Jest to najczęściej używane do przeciążania dla const
, ale może być również stosowane do volatile
i const volatile
.
Jest to konieczne, ponieważ funkcję członka można wywołać tylko wtedy, gdy ma ona co najmniej taką samą kwalifikację cv jak instancja, do której została wywołana. Podczas gdy instancja const
niż const
może wywoływać zarówno elementy const
jak i non- const
, instancja const
może wywoływać tylko elementy const
. Dzięki temu funkcja może mieć różne zachowanie w zależności od kwalifikatorów cv instancji wywołującej, a programiści nie zezwalają na funkcje niepożądanym kwalifikatorom cv, nie dostarczając wersji z tymi kwalifikatorami.
Klasa z jakiegoś podstawowego print
metodą może być const
przeciążony tak:
#include <iostream>
class Integer
{
public:
Integer(int i_): i{i_}{}
void print()
{
std::cout << "int: " << i << std::endl;
}
void print() const
{
std::cout << "const int: " << i << std::endl;
}
protected:
int i;
};
int main()
{
Integer i{5};
const Integer &ic = i;
i.print(); // prints "int: 5"
ic.print(); // prints "const int: 5"
}
Jest to kluczowa zasada poprawności const
: Oznaczając funkcje const
jako const
, można je wywoływać w instancjach const
, co z kolei pozwala funkcjom przyjmować instancje jako wskaźniki / referencje const
jeśli nie trzeba ich modyfikować. Pozwala to kodowi określić, czy modyfikuje stan, przyjmując niezmodyfikowane parametry jako const
i zmodyfikowane parametry bez kwalifikatorów cv, dzięki czemu kod jest bezpieczniejszy i bardziej czytelny.
class ConstCorrect
{
public:
void good_func() const
{
std::cout << "I care not whether the instance is const." << std::endl;
}
void bad_func()
{
std::cout << "I can only be called on non-const, non-volatile instances." << std::endl;
}
};
void i_change_no_state(const ConstCorrect& cc)
{
std::cout << "I can take either a const or a non-const ConstCorrect." << std::endl;
cc.good_func(); // Good. Can be called from const or non-const instance.
cc.bad_func(); // Error. Can only be called from non-const instance.
}
void const_incorrect_func(ConstCorrect& cc)
{
cc.good_func(); // Good. Can be called from const or non-const instance.
cc.bad_func(); // Good. Can only be called from non-const instance.
}
Częstym zastosowaniem tego jest deklarowanie akcesorów jako const
, a mutatorów jako non- const
.
Żadnych członków klasy nie można modyfikować w ramach funkcji const
członka. Jeśli istnieje element, który naprawdę musisz zmodyfikować, na przykład zablokowanie std::mutex
, możesz zadeklarować go jako mutable
:
class Integer
{
public:
Integer(int i_): i{i_}{}
int get() const
{
std::lock_guard<std::mutex> lock{mut};
return i;
}
void set(int i_)
{
std::lock_guard<std::mutex> lock{mut};
i = i_;
}
protected:
int i;
mutable std::mutex mut;
};