C++
Перегрузка функции
Поиск…
Вступление
См. Также отдельную тему о разрешении перегрузки
замечания
Неоднозначность может возникать, когда один тип может быть неявно преобразован в несколько типов, и для этого конкретного типа нет соответствующей функции.
Например:
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
Что такое перегрузка функций?
Перегрузка функций имеет несколько функций, объявленных в одной и той же области с тем же именем, существующим в одном и том же месте (известном как область действия ), отличающимся только их сигнатурой , что означает аргументы, которые они принимают.
Предположим, вы пишете ряд функций для обобщенных возможностей печати, начиная с std::string
:
void print(const std::string &str)
{
std::cout << "This is a string: " << str << std::endl;
}
Это прекрасно работает, но предположим, что вам нужна функция, которая также принимает int
и печатает это тоже. Вы могли бы написать:
void print_int(int num)
{
std::cout << "This is an int: " << num << std::endl;
}
Но поскольку две функции принимают разные параметры, вы можете просто написать:
void print(int num)
{
std::cout << "This is an int: " << num << std::endl;
}
Теперь у вас есть 2 функции, оба с именем print
, но с разными сигнатурами. Один принимает std::string
, другой - int
. Теперь вы можете позвонить им, не беспокоясь о разных именах:
print("Hello world!"); //prints "This is a string: Hello world!"
print(1337); //prints "This is an int: 1337"
Вместо:
print("Hello world!");
print_int(1337);
Когда у вас есть перегруженные функции, компилятор указывает, какую из функций вызывать из параметров, которые вы ему предоставляете. Необходимо соблюдать осторожность при записи перегрузок функций. Например, с неявными преобразованиями типов:
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;
}
Теперь не сразу понятно, какая перегрузка print
вызывается при написании:
print(5);
И вам может понадобиться дать вашему компилятору некоторые подсказки, например:
print(static_cast<double>(5));
print(static_cast<int>(5));
print(5.0);
Некоторая забота также должна быть предпринята при написании перегрузок, которые принимают необязательные параметры:
// 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;
}
Поскольку компилятор не может определить, предназначен ли вызов типа print(17)
для первой или второй функции из-за необязательного второго параметра, это не скомпилируется.
Тип возврата в функции Перегрузка
Обратите внимание, что вы не можете перегружать функцию на основе ее возвращаемого типа. Например:
// WRONG CODE
std::string getValue()
{
return "hello";
}
int getValue()
{
return 0;
}
int x = getValue();
Это вызовет ошибку компиляции, поскольку компилятор не сможет определить, какую версию getValue
вызывать, даже если тип возвращаемого значения присваивается int
.
Функция члена cv-qualifier Перегрузка
Функции внутри класса могут быть перегружены, когда они доступны через cv-квалифицированную ссылку на этот класс; это чаще всего используется для перегрузки для const
, но также может использоваться для перегрузки для volatile
и const volatile
. Это связано с тем, что все нестатические функции-члены принимают this
как скрытый параметр, к которому применяются cv-квалификаторы. Это чаще всего используется для перегрузки для const
, но также может использоваться для volatile
и const volatile
.
Это необходимо, потому что функция-член может быть вызвана только в том случае, если она, по меньшей мере, cv-квалифицирована как экземпляр, на который он вызван. В то время как экземпляр non const
может вызывать как const
и не const
члены, экземпляр const
может вызывать только const
члены. Это позволяет функции иметь различное поведение в зависимости от cv-квалификаторов вызывающего экземпляра и позволяет программисту запрещать функции для нежелательного cv-квалификатора (ов), не предоставляя версию с этим квалификатором.
Класс с некоторым основным методом print
может быть const
перегружен следующим образом:
#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"
}
Это ключевой принцип корректности const
: помечая функции-члены как const
, они могут быть вызваны в экземплярах const
, что, в свою очередь, позволяет функциям принимать экземпляры как указатели / ссылки const
если им не нужно их изменять. Это позволяет коду определять, изменяет ли он состояние, принимая немодифицированные параметры как const
и измененные параметры без cv-квалификаторов, делая код более безопасным и читаемым.
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.
}
Общим применением этого является объявление аксессоров как const
, а мутаторы - как const
.
Никакие члены класса не могут быть изменены внутри функции const
member. Если есть какой-то член, который вам действительно нужно изменить, например блокировку std::mutex
, вы можете объявить его как 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;
};