Перегрузка функций
C++ позволяет указать несколько функций одного и того же имени в одной область. Эти функции называются перегруженными функциями или перегрузками. Перегруженные функции позволяют предоставлять различные семантики для функции в зависимости от типов и количества его аргументов.
Например, рассмотрим print
функцию, принимающую std::string
аргумент. Эта функция может выполнять очень разные задачи, отличные от функции, которая принимает аргумент типа double
. Перегрузка позволяет использовать такие имена, как print_string
или print_double
. Во время компиляции компилятор выбирает перегрузку, используемую на основе типов и количества аргументов, переданных вызывающим оператором. При вызове print(42.0)
void print(double d)
вызывается функция. При вызове print("hello world")
вызывается перегрузка void print(std::string)
.
Вы можете перегрузить функции-члены и бесплатные функции. В следующей таблице показано, какие части объявления функции C++ используются для различения групп функций с тем же именем в той же область.
Заметки по перегрузке
Элемент объявления функции | Используется для перегрузки? |
---|---|
Тип возвращаемого функцией значения | No |
Число аргументов | Да |
Тип аргументов | Да |
Наличие или отсутствие многоточия | Да |
typedef Использование имен |
No |
Незаданные границы массива | No |
const или volatile |
Да, при применении ко всей функции |
Ссылочные квалификаторы (& и && ) |
Да |
Пример
В следующем примере показано, как использовать перегрузки функций:
// function_overloading.cpp
// compile with: /EHsc
#include <iostream>
#include <math.h>
#include <string>
// Prototype three print functions.
int print(std::string s); // Print a string.
int print(double dvalue); // Print a double.
int print(double dvalue, int prec); // Print a double with a
// given precision.
using namespace std;
int main(int argc, char *argv[])
{
const double d = 893094.2987;
if (argc < 2)
{
// These calls to print invoke print( char *s ).
print("This program requires one argument.");
print("The argument specifies the number of");
print("digits precision for the second number");
print("printed.");
exit(0);
}
// Invoke print( double dvalue ).
print(d);
// Invoke print( double dvalue, int prec ).
print(d, atoi(argv[1]));
}
// Print a string.
int print(string s)
{
cout << s << endl;
return cout.good();
}
// Print a double in default precision.
int print(double dvalue)
{
cout << dvalue << endl;
return cout.good();
}
// Print a double in specified precision.
// Positive numbers for precision indicate how many digits
// precision after the decimal point to show. Negative
// numbers for precision indicate where to round the number
// to the left of the decimal point.
int print(double dvalue, int prec)
{
// Use table-lookup for rounding/truncation.
static const double rgPow10[] = {
10E-7, 10E-6, 10E-5, 10E-4, 10E-3, 10E-2, 10E-1,
10E0, 10E1, 10E2, 10E3, 10E4, 10E5, 10E6 };
const int iPowZero = 6;
// If precision out of range, just print the number.
if (prec < -6 || prec > 7)
{
return print(dvalue);
}
// Scale, truncate, then rescale.
dvalue = floor(dvalue / rgPow10[iPowZero - prec]) *
rgPow10[iPowZero - prec];
cout << dvalue << endl;
return cout.good();
}
В приведенном выше коде показаны перегрузки print
функции в область файла.
Аргумент по умолчанию не считается частью типа функции. Поэтому он не используется при выборе перегруженных функций. Две функции, которые различаются только в своих аргументах, считаются множественными определениями, а не перегруженными функциями.
Аргументы по умолчанию не могут быть предоставлены для перегруженных операторов.
Сопоставление аргументов
Компилятор выбирает перегруженную функцию для вызова на основе наилучшего соответствия между объявлениями функций в текущем область с аргументами, указанными в вызове функции. Если подходящая функция найдена, эта функция вызывается. "Подходит" в этом контексте означает:
Точное соответствие найдено.
Тривиальное преобразование выполнено.
Восходящее приведение целого типа выполнено.
Стандартное преобразование в требуемый тип аргумента существует.
Определяемое пользователем преобразование (оператор преобразования или конструктор) в нужный тип аргумента существует.
Аргументы, представленные многоточием, найдены.
Компилятор создает набор функций-кандидатов для каждого аргумента. Функции-кандидаты — это функции, в которых фактический аргумент в данной позиции можно преобразовать в тип формального аргумента.
Для каждого аргумента создается набор наиболее подходящих функций, и выбранная функция представляет собой пересечение всех наборов. Если на пересечении находится несколько функций, перегрузка является неоднозначной и выдает ошибку. Выбранная в конечном итоге функция всегда лучше, чем каждая другая функция в группе по крайней мере для одного аргумента. Если нет четкого победителя, вызов функции создает ошибку компилятора.
Рассмотрим следующие объявления (функции отмечены как Variant 1
, Variant 2
и Variant 3
для ссылки в последующем обсуждении).
Fraction &Add( Fraction &f, long l ); // Variant 1
Fraction &Add( long l, Fraction &f ); // Variant 2
Fraction &Add( Fraction &f, Fraction &f ); // Variant 3
Fraction F1, F2;
Рассмотрим следующий оператор.
F1 = Add( F2, 23 );
Представленный выше оператор создает два набора.
Set 1: Кандидат функции, имеющие первый аргумент типа Fraction |
Set 2: Кандидат функций, второй аргумент которого можно преобразовать в тип int |
---|---|
Variant 1 | Вариант 1 (int можно преобразовать в long использование стандартного преобразования) |
Variant 3 |
Функции в Set 2 — это функции, которые имеют неявные преобразования из фактического типа параметра в формальный тип параметра. Одна из этих функций имеет наименьшую "стоимость" для преобразования фактического типа параметра в соответствующий формальный тип параметра.
Пересечением этих двух наборов является функция Variant 1. Ниже представлен пример неоднозначного вызова функции.
F1 = Add( 3, 6 );
В предыдущем вызове функции создаются следующие наборы.
Задать 1. Кандидаты функции с первым аргументом типа int |
Задать 2. Кандидаты функции, имеющие второй аргумент типа int |
---|---|
Вариант 2 (int можно преобразовать в long использование стандартного преобразования) |
Вариант 1 (int можно преобразовать в long использование стандартного преобразования) |
Так как пересечение этих двух наборов пусто, компилятор создает сообщение об ошибке.
Для сопоставления аргументов функция с n аргументами по умолчанию обрабатывается как n+1 отдельных функций, каждая из которых имеет другое количество аргументов.
Многоточие (...
) действует как дикий карта; он соответствует любому фактическому аргументу. Это может привести к множеству неоднозначных наборов, если вы не разрабатываете перегруженные наборы функций с чрезвычайной осторожностью.
Примечание.
Неоднозначность перегруженных функций не может быть определена до тех пор, пока не будет обнаружен вызов функции. На этом этапе наборы создаются для каждого аргумента в вызове функции, и можно определить, существует ли неоднозначная перегрузка. Это означает, что неоднозначность может оставаться в коде до тех пор, пока они не вызываются определенным вызовом функции.
Различия типов аргументов
Перегруженные функции различают типы аргументов, имеющие разные инициализаторы. Следовательно, аргумент заданного типа и ссылка на этот тип считаются одинаковыми для перегрузки, Они считаются одинаковыми, так как они принимают те же инициализаторы. Например, max( double, double )
— то же самое, что и max( double &, double & )
. Объявление двух таких функций приводит к ошибке.
По той же причине аргументы функций типа, измененные const
или volatile
не обрабатываются иначе, чем базовый тип в целях перегрузки.
Однако механизм перегрузки функций может различать ссылки, которые квалифицированы const
и volatile
ссылаются на базовый тип. Это делает код таким, как можно сделать следующее:
// argument_type_differences.cpp
// compile with: /EHsc /W3
// C4521 expected
#include <iostream>
using namespace std;
class Over {
public:
Over() { cout << "Over default constructor\n"; }
Over( Over &o ) { cout << "Over&\n"; }
Over( const Over &co ) { cout << "const Over&\n"; }
Over( volatile Over &vo ) { cout << "volatile Over&\n"; }
};
int main() {
Over o1; // Calls default constructor.
Over o2( o1 ); // Calls Over( Over& ).
const Over o3; // Calls default constructor.
Over o4( o3 ); // Calls Over( const Over& ).
volatile Over o5; // Calls default constructor.
Over o6( o5 ); // Calls Over( volatile Over& ).
}
Выходные данные
Over default constructor
Over&
Over default constructor
const Over&
Over default constructor
volatile Over&
Указатели на const
volatile
объекты также считаются разными от указателей к базовому типу в целях перегрузки.
Сопоставление аргументов и преобразования
Когда компилятор пытается сопоставить фактические аргументы с аргументами в объявлениях функций и точное соответствие найти не удается, для получения правильного типа он может выполнять стандартные или пользовательские преобразования. Для преобразований действуют следующие правила:
Последовательности преобразований, содержащих несколько определяемых пользователем преобразований, не рассматриваются.
Последовательности преобразований, которые могут быть сокращены путем удаления промежуточных преобразований, не рассматриваются.
Результирующая последовательность преобразований, если она есть, называется лучшей последовательностью сопоставления. Существует несколько способов преобразования объекта типа в тип int
unsigned long
с помощью стандартных преобразований (описанных в стандартных преобразованиях):
Преобразуйте из
int
long
иunsigned long
затем изlong
.Преобразование из
int
unsigned long
.
Хотя первая последовательность достигает желаемой цели, она не является лучшей последовательностью сопоставления, так как короткая последовательность существует.
В следующей таблице показана группа преобразований, называемых тривиальными преобразованиями. Тривиальные преобразования имеют ограниченное влияние на то, на какую последовательность компилятор выбирает в качестве лучшего соответствия. Эффект тривиальных преобразований описывается после таблицы.
Тривиальные преобразования
Тип аргумента | Преобразованный тип |
---|---|
type-name |
type-name& |
type-name& |
type-name |
type-name[] |
type-name* |
type-name(argument-list) |
(*type-name)(argument-list) |
type-name |
const type-name |
type-name |
volatile type-name |
type-name* |
const type-name* |
type-name* |
volatile type-name* |
Ниже приведена последовательность, в которой делаются попытки выполнения преобразований.
Точное соответствие. Точное соответствие между типами, с которыми функция вызывается, и типами, объявленными в прототипе функции, всегда является наилучшим соответствием. Последовательности тривиальных преобразований классифицируются как точные соответствия. Однако последовательности, которые не делают из этих преобразований, считаются лучше, чем последовательности, которые преобразуют:
От указателя до указателя на
const
(type-name*
наconst type-name*
).От указателя до указателя на
volatile
(type-name*
наvolatile type-name*
).Ссылка на
const
(type-name&
toconst type-name&
).Ссылка на
volatile
(type-name&
tovolatile type&
).
Сопоставление с использованием повышений. Любая последовательность, которая не классифицируется как точное совпадение, содержащее только целые рекламные акции, преобразования из
float
double
, а тривиальные преобразования классифицируются как совпадение с использованием рекламных акций. Хотя сопоставление с использованием повышений не такое хорошее, как точное, оно лучше сопоставления с использованием стандартных преобразований.Сопоставление с использованием стандартных преобразований. Любая последовательность, не классифицированная как точное соответствие или сопоставление с использованием повышений и содержащая только стандартные и тривиальные преобразования, классифицируется как сопоставление с использованием стандартных преобразований. В этой категории применяются следующие правила:
Преобразование из указателя в производный класс к указателю на прямой или косвенный базовый класс предпочтительнее преобразовать в
void *
илиconst void *
.преобразование из указателя на производный класс в указатель на базовый класс создает тем более хорошее соответствие, чем ближе базовый класс к прямому базовому классу. Предположим, что иерархия классов показана на следующем рисунке:
Диаграмма, показывающая предпочитаемые преобразования.
Преобразование из типа D*
в тип C*
предпочтительнее преобразования из типа D*
в тип B*
. Аналогично, преобразование из типа D*
в тип B*
предпочтительнее преобразования из типа D*
в тип A*
.
Это же правило применяется для преобразований ссылок. Преобразование из типа D&
в тип C&
предпочтительнее преобразования из типа D&
в тип B&
и т. д.
Это же правило применяется для преобразований указателей на член. Преобразование из типа T D::*
в тип T C::*
предпочтительнее преобразования из типа T D::*
в тип T B::*
и т. д. (T
— тип члена.)
Предыдущее правило применяется только в определенном пути наследования. Рассмотрим граф, показанный на следующем рисунке.
Диаграмма с несколькими наследованиеми, показывающая предпочитаемые преобразования.
Преобразование из типа C*
в тип B*
предпочтительнее преобразования из типа C*
в тип A*
. Причина заключается в том, что эти преобразования находятся на одном пути и узел B*
ближе. Однако преобразование из типа в тип не предпочтительнее для преобразования в тип C*
D*
A*
; нет предпочтений, так как преобразования следуют разным путям.
Сопоставление с пользовательскими преобразованиями. Эта последовательность не может быть классифицирована как точное совпадение, совпадение с использованием рекламных акций или совпадения с использованием стандартных преобразований. Чтобы классифицироваться как совпадение с пользовательскими преобразованиями, последовательность должна содержать только определяемые пользователем преобразования, стандартные преобразования или тривиальные преобразования. Совпадение с определяемыми пользователем преобразованиями считается лучшим, чем совпадение с многоточием (
...
), но не так хорошо, как совпадение со стандартными преобразованиями.Сопоставление с многоточием. Любая последовательность, соответствующая многоточию в объявлении, классифицируется как сопоставление с многоточием. Считается самым слабым матчем.
Пользовательские преобразования применяются при отсутствии встроенного повышения или преобразования. Эти преобразования выбираются на основе типа соответствующего аргумента. Рассмотрим следующий код:
// argument_matching1.cpp
class UDC
{
public:
operator int()
{
return 0;
}
operator long();
};
void Print( int i )
{
};
UDC udc;
int main()
{
Print( udc );
}
Доступные пользовательские преобразования для класса UDC
относятся к типу и типу int
long
. Поэтому компилятор проверяет преобразования для типа сопоставляемого объекта: UDC
. Преобразование, необходимое для int
существования, и выбрано.
В процессе сопоставления аргументов стандартные преобразования можно применять как к аргументу, так и к результату пользовательского преобразования. Поэтому следующий код работает.
void LogToFile( long l );
...
UDC udc;
LogToFile( udc );
В этом примере компилятор вызывает определяемое пользователем преобразование, operator long
чтобы преобразовать udc
в тип long
. Если определяемое пользователем преобразование в тип long
не определено, компилятор сначала преобразует тип в тип UDC
int
с помощью определяемого operator int
пользователем преобразования. Затем оно будет применять стандартное преобразование из типа в тип int
long
, чтобы соответствовать аргументу в объявлении.
Если для сопоставления аргумента требуются какие-либо определяемые пользователем преобразования, стандартные преобразования не используются при оценке наилучшего соответствия. Даже если для нескольких кандидатов требуется преобразование, определяемое пользователем, функции считаются равными. Например:
// argument_matching2.cpp
// C2668 expected
class UDC1
{
public:
UDC1( int ); // User-defined conversion from int.
};
class UDC2
{
public:
UDC2( long ); // User-defined conversion from long.
};
void Func( UDC1 );
void Func( UDC2 );
int main()
{
Func( 1 );
}
Обе версии Func
требуют преобразования, определяемого пользователем, для преобразования типа int
в аргумент типа класса. Возможные преобразования:
Преобразование из типа в тип
int
UDC1
(определяемое пользователем преобразование).Преобразование из типа в тип
int
long
; затем преобразование в типUDC2
(двухфакторное преобразование).
Несмотря на то что второй требуется как стандартное преобразование, так и определяемое пользователем преобразование, эти два преобразования по-прежнему считаются равными.
Примечание.
Определяемые пользователем преобразования считаются преобразованием путем построения или преобразования путем инициализации. Компилятор считает оба метода равными, когда он определяет лучшее совпадение.
Сопоставление аргументов this
и указатель
Функции-члены класса обрабатываются по-разному в зависимости от того, объявляются ли они как static
. static
Функции не имеют неявного аргумента, который предоставляет this
указатель, поэтому они считаются одним менее аргументом, чем обычные функции-члены. В противном случае они объявляются одинаково.
Функции-члены, которые не static
требуют подразумеваемого this
указателя, чтобы соответствовать типу объекта, который вызывается функцией. Кроме того, для перегруженных операторов требуется первый аргумент для сопоставления объекта, к которым применяется оператор. Дополнительные сведения о перегруженных операторах см. в разделе "Перегруженные операторы".
В отличие от других аргументов в перегруженных функциях, компилятор вводит временные объекты и пытается не преобразовании при попытке сопоставить this
аргумент указателя.
->
Если оператор выбора члена используется для доступа к функции-члену классаclass_name
, this
аргумент указателя имеет типclass_name * const
. Если члены объявлены как const
или volatile
, типы и const class_name * const
volatile class_name * const
соответственно.
Оператор выбора члена .
работает точно так же, за исключением того, что в качестве префикса к имени объекта подставляется неявный оператор взятия адреса &
. В следующем примере показано, как это делается:
// Expression encountered in code
obj.name
// How the compiler treats it
(&obj)->name
С точки зрения сопоставления аргументов, левый операнд операторов ->*
и .*
(указатель на член) обрабатывается так же, как и для операторов .
и ->
(выбор члена).
Квалификаторы ссылок на функции-члены
Ссылочные квалификаторы позволяют перегружать функцию-член на основе того, указывает this
ли объект на rvalue или lvalue. Используйте эту функцию, чтобы избежать ненужных операций копирования в сценариях, где вы решили не предоставлять указатель доступа к данным. Например, предположим, что класс C
инициализирует некоторые данные в конструкторе и возвращает копию этих данных в функции-члене get_data()
. Если объект типа C
является rvalue, который будет уничтожен, компилятор выбирает перегрузку, которая перемещается get_data() &&
вместо копирования данных.
#include <iostream>
#include <vector>
using namespace std;
class C
{
public:
C() {/*expensive initialization*/}
vector<unsigned> get_data() &
{
cout << "lvalue\n";
return _data;
}
vector<unsigned> get_data() &&
{
cout << "rvalue\n";
return std::move(_data);
}
private:
vector<unsigned> _data;
};
int main()
{
C c;
auto v = c.get_data(); // get a copy. prints "lvalue".
auto v2 = C().get_data(); // get the original. prints "rvalue"
return 0;
}
Ограничения на перегрузку
К допустимому набору перегруженных функций применяется несколько ограничений.
Любые две функции в наборе перегруженных функций должны иметь разные списки аргументов.
Перегрузка функций с списками аргументов одного типа, основанных только на типе возврата, является ошибкой.
Блок, относящийся только к системам Майкрософт
Вы можете перегружаться
operator new
на основе возвращаемого типа, в частности, на основе модификатора модели памяти.Завершение блока, относящегося только к системам Майкрософт
Функции-члены не могут быть перегружены исключительно потому, что один из них
static
и другой неstatic
является.typedef
объявления не определяют новые типы; они вводят синонимы для существующих типов. Они не влияют на механизм перегрузки. Рассмотрим следующий код:typedef char * PSTR; void Print( char *szToPrint ); void Print( PSTR szToPrint );
Две указанные выше функции имеют идентичные списки аргументов.
PSTR
является синонимом типаchar *
. В области члена этот код возвращает ошибку.Перечисляемые типы являются отдельными типами и могут использоваться для различения перегруженных функций.
Типы "массив" и "указатель на" считаются идентичными для целей различения перегруженных функций, но только для одномерных массивов. Эти перегруженные функции конфликтуют и создают сообщение об ошибке:
void Print( char *szToPrint ); void Print( char szToPrint[] );
Для массивов более высоких измерений второй и более поздний измерения считаются частью типа. Они используются для различения перегруженных функций:
void Print( char szToPrint[] ); void Print( char szToPrint[][7] ); void Print( char szToPrint[][9][42] );
Перегрузка, переопределение и скрытие
Любые два объявления функций с одинаковым именем в одной и той же область могут ссылаться на одну и ту же функцию или две дискретные перегруженные функции. Если списки аргументов в объявлениях содержат аргументы эквивалентных типов (как описано в предыдущем разделе), эти объявления относятся к одной и той же функции. В противном случае они ссылаются на две различные функции, которые выбираются с использованием перегрузки.
Класс область строго наблюдается. Функция, объявленная в базовом классе, не имеет того же область, что и функция, объявленная в производном классе. Если функция в производном классе объявляется с тем же именем, что virtual
и функция в базовом классе, функция производного класса переопределяет функцию базового класса. Дополнительные сведения см. в разделе "Виртуальные функции".
Если функция базового класса не объявлена как virtual
, то производная функция класса, как утверждается , скрывает ее. Переопределение и скрытие отличаются от перегрузки.
Блок область строго наблюдается. Функция, объявленная в файле область, не в том же область, что и функция, объявленная локально. Если локально объявленная функция имеет то же имя, что и функция, объявленная в области файла, локально объявленная функция скрывает функцию области файла, не вызывая перегрузки. Например:
// declaration_matching1.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
void func( int i )
{
cout << "Called file-scoped func : " << i << endl;
}
void func( char *sz )
{
cout << "Called locally declared func : " << sz << endl;
}
int main()
{
// Declare func local to main.
extern void func( char *sz );
func( 3 ); // C2664 Error. func( int ) is hidden.
func( "s" );
}
В предыдущем коде показаны два определения функции func
. Определение, которое принимает аргумент типа char *
, является локальным main
из-за инструкции extern
. Таким образом, определение, которое принимает аргумент типа int
, скрыто, и первый вызов func
находится в ошибке.
В случае перегруженных функций-членов различным версиям функции могут предоставляться разные права доступа. Они по-прежнему считаются в область заключенного класса и, следовательно, перегружены функциями. Рассмотрим следующий код, в котором функция-член Deposit
перегружена; одна версия является открытой, вторая — закрытой.
Целью кода в примере является предоставление класса Account
, в котором для внесения средств требуется правильный пароль. Это делается с помощью перегрузки.
Вызов Deposit
при Account::Deposit
вызове функции частного члена. Этот вызов является правильным, так как Account::Deposit
является функцией-членом и имеет доступ к частным членам класса.
// declaration_matching2.cpp
class Account
{
public:
Account()
{
}
double Deposit( double dAmount, char *szPassword );
private:
double Deposit( double dAmount )
{
return 0.0;
}
int Validate( char *szPassword )
{
return 0;
}
};
int main()
{
// Allocate a new object of type Account.
Account *pAcct = new Account;
// Deposit $57.22. Error: calls a private function.
// pAcct->Deposit( 57.22 );
// Deposit $57.22 and supply a password. OK: calls a
// public function.
pAcct->Deposit( 52.77, "pswd" );
}
double Account::Deposit( double dAmount, char *szPassword )
{
if ( Validate( szPassword ) )
return Deposit( dAmount );
else
return 0.0;
}
См. также
Обратная связь
https://aka.ms/ContentUserFeedback.
Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделеОтправить и просмотреть отзыв по