базовые концепции ООП — перегрузка функцийbaseline OOP concepts — function overloading
Перегрузка — несколько функций с одним именем, но разными сигнатурами (типами/числом параметров), между которыми компилятор выбирает одну на этапе компиляции. Выбор — это не «первое совпадение», а формализованный процесс ранжирования кандидатов с точными правилами приоритета, включающий неявные преобразования типов.Overloading is several functions sharing one name but different signatures (parameter types/count), among which the compiler picks exactly one at compile time. The choice isn't "first match" — it's a formalized ranking process over the candidates, with precise priority rules that include implicit type conversions.
Сигнатура функции — её имя плюс типы и порядок параметров (возвращаемый тип в сигнатуру не входит — по нему нельзя перегружать функции). Перегрузка (overloading) — механизм, позволяющий объявить несколько функций с одинаковым именем, но разными сигнатурами; все они образуют overload set — множество кандидатов, из которого компилятор выбирает одну функцию под конкретный вызов.A function's signature is its name plus the types and order of its parameters (the return type isn't part of the signature — functions can't be overloaded on it alone). Overloading is the mechanism that lets several functions share a name but differ in signature; together they form an overload set — the pool of candidates the compiler picks exactly one from for a given call.
void print(int x) { std::cout << "int: " << x; }
void print(double x) { std::cout << "double: " << x; }
void print(const std::string& x) { std::cout << "string: " << x; }
print(42); // вызовет print(int)// calls print(int)
print(3.14); // вызовет print(double)// calls print(double)
print("hi"s); // вызовет print(const string&)// calls print(const string&)
Для каждого кандидата компилятор ранжирует, насколько «дёшево» аргумент вызова преобразуется к типу параметра, и выбирает кандидата с наименьшей суммарной стоимостью преобразований по всем аргументам. Упрощённый порядок предпочтения (от лучшего к худшему):For every candidate, the compiler ranks how "cheaply" each call argument converts to the parameter's type, and picks the candidate with the lowest total conversion cost across all arguments. A simplified order of preference (best to worst):
Тип аргумента совпадает с типом параметра без каких-либо преобразований (или требует только тривиальных — lvalue-to-rvalue, добавление const).The argument's type matches the parameter's type with no conversion at all (or only trivial ones — lvalue-to-rvalue, adding const).
Безопасное расширение "маленького" типа до "большего" внутри той же категории: char→int, float→double.A safe widening of a "smaller" type to a "larger" one within the same category: char→int, float→double.
Преобразования между разными арифметическими типами (int→double), указатель на производный класс → указатель на базовый и т.п.Conversions between different arithmetic types (int→double), a pointer to a derived class → a pointer to the base, and similar.
Использование неexplicit конструктора одного параметра или оператора преобразования типа (operator T()) — самая дорогая категория из тех, что компилятор ещё готов применить неявно.Using a non-explicit single-argument constructor or a conversion operator (operator T()) — the most expensive category the compiler is still willing to apply implicitly.
Если после ранжирования остаётся ровно один кандидат с наилучшей суммарной стоимостью — он и выбирается. Если несколько кандидатов оказываются равно хороши для данного вызова — это ошибка компиляции «неоднозначный вызов» (ambiguous call), а не случайный выбор одного из них.If exactly one candidate ends up with the best total cost after ranking, that one is chosen. If several candidates tie for equally good, that's an "ambiguous call" compile error — never an arbitrary pick among them.
void handle(long x) { std::cout << "long"; }
void handle(double x) { std::cout << "double"; }
handle(5); // "long" — int→long это стандартное преобразование, int→double тоже,// "long" — int→long is a standard conversion, and so is int→double,
// но int→long считается более "близким" (той же категории — целочисленное)// but int→long is ranked "closer" (same category — integral)
Это именно тот механизм, который делает explicit на одноаргументных конструкторах важным (см. страницу про конструкторы): без него конструктор участвует в overload resolution как ещё один источник кандидатов через пользовательское преобразование, что может привести к вызову неожиданной перегрузки без единой ошибки компиляции.This is exactly the mechanism that makes explicit on single-argument constructors matter (see the constructors page): without it, a constructor joins overload resolution as another source of candidates via user-defined conversion, which can trigger an unexpected overload with no compile error at all.
Возвращаемый тип не входит в сигнатуру, поэтому int f() и double f() в одной области видимости — ошибка компиляции «redefinition», а не перегрузка. Разрешение перегрузки в C++ никогда не учитывает то, во что присваивается результат вызова.The return type isn't part of the signature, so int f() and double f() in the same scope is a "redefinition" compile error, not an overload. C++ overload resolution never takes into account what the call's result is assigned into.
Перегрузка разрешается статически, на этапе компиляции, по видимым в данной точке программы сигнатурам — переопределение виртуальной функции в наследнике (динамическая диспетчеризация) не участвует в overload resolution вообще: сначала компилятор выбирает сигнатуру статически, и лишь затем, если выбранная функция virtual, происходит рантайм-диспетчеризация к её конкретной реализации.Overloading is resolved statically, at compile time, based on signatures visible at that point in the program — overriding a virtual function in a descendant (dynamic dispatch) plays no role in overload resolution at all: the compiler picks a signature statically first, and only then, if the chosen function is virtual, does runtime dispatch kick in to find its concrete implementation.
Если производный класс объявляет метод с именем, совпадающим с именем перегруженного метода базы, все перегрузки базового класса с этим именем становятся невидимыми в производном классе (name hiding) — даже те, чья сигнатура не совпадает с новой. Решение — using Base::methodName; в производном классе, явно возвращающее видимость нужных перегрузок.If a derived class declares a method whose name matches an overloaded base-class method's name, all base-class overloads under that name become invisible in the derived class (name hiding) — even ones whose signature doesn't match the new one. The fix is using Base::methodName; in the derived class, explicitly restoring visibility for the needed overloads.