Задача на перегрузку функций в C++, которая может оказаться сложнее, чем выглядит


Оглавление (нажмите, чтобы открыть):

Задача на перегрузку функций в C++, которая может оказаться сложнее, чем выглядит

Перегрузка функций позволяет вашим программам определять несколько функций с одним и тем же именем и типом возвращаемого значения. Например, следующая программа перегружает функцию с именем — add_values. -Первое определение функции складывает два значения типа — int. -Второе определение функции складывает три значения. В процессе компиляции C++ корректно определяет функцию, которую необходимо использовать:

int add_values(int a,int b)

int add_values (int a, int b, int c)

<
— — -cout &lt-&lt- «200 + 801 = » &lt-&lt- add_values(200, 801) &lt-&lt- endl-
— — -cout &lt-&lt- «100 + 201 + 700 = » &lt-&lt- add_values(100, 201, 700) &lt-&lt- endl-
>

Как видите, программа определяет две функции с именами — add_values —Первая функция складывает два значения типа —int, -в то время как вторая складывает три значения. Вы не обязаны что-либо предпринимать специально для того, чтобы предупредить компилятор о перегрузке, просто используйте ее. Компилятор разгадает, какую функцию следует использовать, основываясь на предлагаемых программой параметрах.

Подобным образом следующая программа MSG_OVR.CPP перегружает функцию show_message. -Первая функция с именем — show_message -выводит стандартное сообщение, параметры ей не передаются. Вторая выводит передаваемое ей сообщение, а третья выводит два сообщения:

<
— — -cout &lt-&lt- «Стандартное сообщение: » &lt-&lt- «Учимся программировать на C++» &lt-&lt- endl-
>

void show_message(char *message)

<
— — -cout &lt-&lt- message &lt-&lt- endl-
>

void show_message(char *first, char *second)

<
— — -cout &lt-&lt- first &lt-&lt- endl-
— — -cout &lt-&lt- second &lt-&lt- endl-
>

<
— — -show_message()-
— — -show_message(«Учимся программировать на языке C++!»)-
— — -show_message(«B C++ нет предрассудков!»,»Перегрузка — это круто!») —
>

КОГДА НЕОБХОДИМА ПЕРЕГРУЗКА

Одним из наиболее общих случаев использования перегрузки является применение функции для получения определенного результата, исходя из различных параметров. Например, предположим, что в вашей программе есть функция с именем — day_of_week, -которая возвращает текущий день недели (0 для воскресенья, 1 для понедельника, …, 6 для субботы). Ваша программа могла бы перегрузить эту функцию таким образом, чтобы она верно возвращала день недели, если ей передан юлианский день в качестве параметра, или если ей переданы день, месяц и год:

int day_of_week(int julian_day)

int day_of_week(int month, int day, int year)

По мере изучения объектно-ориентированного программирования в C++, представленного в следующих уроках, вы будете использовать перегрузку функций для расширения возможностей своих программ.

Перегрузка функций улучшает удобочитаемость программ

Перегрузка функций C++ позволяет вашим программам определять несколько функций с одним и тем же именем. Перегруженные функции должны возвращать значения одинакового типа*, но могут отличаться количеством и типом параметров. До появления перегрузки функций в C++ программисты языка С должны были создавать несколько функций с почти одинаковыми именами. К сожалению программисты, желающие использовать такие функции, должны были помнить, какая комбинация параметров соответствует какой функции. С другой стороны, перегрузка функций упрощает задачу программистов, требуя, чтобы они помнили только одно имя функции.

* Перегруженные функции не обязаны возвращать значения одинакового типа по той причине, что компилятор однозначно идентифицирует функцию по ее имени и набору ее аргументов. Для компилятора функции с одинаковыми именами, но различными типами аргументов — разные функции, поэтому тип возвращаемого значения — прерогатива каждой функции. — Прим.перев.

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Перегрузка функций позволяет вам указать несколько определений для одной и той же функции. В процессе компиляции C++ определит, какую функцию следует использовать, основываясь на количестве и типе передаваемых параметров. Из данного урока вы узнали, что перегружать функции достаточно просто. Из урока 14 вы узнаете, как ссылки C++ упрощают процесс изменения параметров внутри функций. Однако, прежде чем перейти к уроку 14, убедитесь, что вы изучили следующие основные концепции:

    1. Перегрузка функций предоставляет несколько «взглядов» на одну и ту же функцию внутри вашей программы.
    2. Для перегрузки функций просто определите несколько функций с одним и тем же именем и типом возвращаемого значения, которые отличаются только количеством и типом параметров.
    3. В процессе компиляции C++ определит, какую функцию следует вызвать, основываясь на количестве и типе передаваемых параметров.
    4. Перегрузка функций упрощает программирование, позволяя программистам работать только с одним именем функции.

Задачи , построенные с использованием функций С++ [закрыт]

Доброго времени суток форумчане ! «Подтолкните» в решении следующих задач , я забуксовал

Реализовать функцию, которая принимает массив (плюс его длину) , а также число n , и возвращает индекс числа в массиве или -1 , если такого числа нет. Структура : int Search (int a [], const int n, const int key);

Реализовать функцию, которая принимает массив и выводит на экран те элементы , значение которых являются простыми числами. Структура (IsPrimeNumber) , следовательно я думаю что функция должна выглядеть примерно так: primeNumbersInArray (int a[] , const int);

Реализовать функцию сортировки массива любым алгоритмом : void sortArray (int a[],const int n);

4 Реализовать функцию, которая принимает массив и возвращает индекс его максимального элемента

5 . Аналогично с минимальным элементом.

Р.S. Изучаю С++ не продолжительное время , объяснить решение первых двух задач , остальные похожие думаю я справлюсь , спасибо.

9. Перегруженные функции

Итак, мы уже знаем, как объявлять, определять и использовать функции в программах. В этой главе речь пойдет об их специальном виде – перегруженных функциях. Две функции называются перегруженными, если они имеют одинаковое имя, объявлены в одной и той же области видимости, но имеют разные списки формальных параметров. Мы расскажем, как объявляются такие функции и почему они полезны. Затем мы рассмотрим вопрос об их разрешении, т.е. о том, какая именно из нескольких перегруженных функций вызывается во время выполнения программы. Эта проблема является одной из наиболее сложных в C++. Тем, кто хочет разобраться в деталях, будет интересно прочитать два раздела в конце главы, где тема преобразования типов аргументов и разрешения перегруженных функций раскрывается более подробно.

9.1. Объявления перегруженных функций

Теперь, научившись объявлять, определять и использовать функции в программах, познакомимся с перегрузкой – еще одним аспектом в C++. Перегрузка позволяет иметь несколько одноименных функций, выполняющих схожие операции над аргументами разных типов.
Вы уже воспользовались предопределенной перегруженной функцией. Например, для вычисления выражения

вызывается операция целочисленного сложения, тогда как вычисление выражения

осуществляет сложение с плавающей точкой. Выбор той или иной операции производится незаметно для пользователя. Операция сложения перегружена, чтобы обеспечить работу с операндами разных типов. Ответственность за распознавание контекста и применение операции, соответствующей типам операндов, возлагается на компилятор, а не на программиста.
В этой главе мы покажем, как определять собственные перегруженные функции.

9.1.1. Зачем нужно перегружать имя функции

Как и в случае со встроенной операцией сложения, нам может понадобиться набор функций, выполняющих одно и то же действие, но над параметрами различных типов. Предположим, что мы хотим определить функции, возвращающие наибольшее из переданных значений параметров. Если бы не было перегрузки, пришлось бы каждой такой функции присвоить уникальное имя. Например, семейство функций max() могло бы выглядеть следующим образом:

Однако все они делают одно и то же: возвращают наибольшее из значений параметров. С точки зрения пользователя, здесь лишь одна операция – вычисление максимума, а детали ее реализации большого интереса не представляют.
Отмеченная лексическая сложность отражает ограничение программной среды: всякое имя, встречающееся в одной и той же области видимости, должно относиться к уникальной сущности (объекту, функции, классу и т.д.). Такое ограничение на практике создает определенные неудобства, поскольку программист должен помнить или каким-то образом отыскивать все имена. Перегрузка функций помогает справиться с этой проблемой.
Применяя перегрузку, программист может написать примерно так:

Этот подход оказывается чрезвычайно полезным во многих ситуациях.

9.1.2. Как перегрузить имя функции

В C++ двум или более функциям может быть дано одно и то же имя при условии, что их списки параметров различаются либо числом параметров, либо их типами. В данном примере мы объявляем перегруженную функцию max():

Для каждого перегруженного объявления требуется отдельное определение функции max() с соответствующим списком параметров.
Если в некоторой области видимости имя функции объявлено более одного раза, то второе (и последующие) объявление интерпретируется компилятором так:

  • если списки параметров двух функций отличаются числом или типами параметров, то функции считаются перегруженными:
  • если тип возвращаемого значения и списки параметров в объявлениях двух функций одинаковы, то второе объявление считается повторным: Имена параметров при сравнении объявлений во внимание не принимаются;
    если списки параметров двух функций одинаковы, но типы возвращаемых значений различны, то второе объявление считается неправильным (несогласованным с первым) и помечается компилятором как ошибка:

Перегруженные функции не могут различаться лишь типами возвращаемого значения; если списки параметров двух функций разнятся только подразумеваемыми по умолчанию значениями аргументов, то второе объявление считается повторным:

Ключевое слово typedef создает альтернативное имя для существующего типа данных, новый тип при этом не создается. Поэтому если списки параметров двух функций различаются только тем, что в одном используется typedef, а в другом тип, для которого typedef служит псевдонимом, такие списки считаются одинаковыми, как, например, в следующих двух объявлениях функции calc(). В таком случае второе объявление даст ошибку компиляции, поскольку возвращаемое значение отличается от указанного раньше:

Спецификаторы const или volatile при подобном сравнении не принимаются во внимание. Так, следующие два объявления считаются одинаковыми:


Спецификатор const важен только внутри определения функции: он показывает, что в теле функции запрещено изменять значение параметра. Однако аргумент, передаваемый по значению, можно использовать в теле функции как обычную инициированную переменную: вне функции изменения не видны. (Способы передачи аргументов, в частности передача по значению, обсуждаются в разделе 7.3.) Добавление спецификатора const к параметру, передаваемому по значению, не влияет на его интерпретацию. Функции, объявленной как f(int), может быть передано любое значение типа int, равно как и функции f(const int). Поскольку они обе принимают одно и то же множество значений аргумента, то приведенные объявления не считаются перегруженными. f() можно определить как

Наличие двух этих определений в одной программе – ошибка, так как одна и та же функция определяется дважды.
Однако, если спецификатор const или volatile применяется к параметру указательного или ссылочного типа, то при сравнении объявлений он учитывается.

9.1.3. Когда не надо перегружать имя функции

В каких случаях перегрузка имени не дает преимуществ? Например, тогда, когда присвоение функциям разных имен облегчает чтение программы. Вот несколько примеров. Следующие функции оперируют одним и тем же абстрактным типом даты. На первый взгляд, они являются подходящими кандидатами для перегрузки:

Эти функции работают с одним типом данных – классом Date, но выполняют семантически различные действия. В этом случае лексическая сложность, связанная с употреблением различных имен, проистекает из принятого программистом соглашения об обеспечении набора операций над типом данных и именования функций в соответствии с семантикой этих операций. Правда, механизм классов C++ делает такое соглашение излишним. Следовало бы сделать такие функции членами класса Date, но при этом оставить разные имена, отражающие смысл операции:

Приведем еще один пример. Следующие пять функций-членов Screen выполняют различные операции над экранным курсором, являющимся принадлежностью того же класса. Может показаться, что разумно перегрузить эти функции под общим названием move():

Впрочем, последние две функции перегрузить нельзя, так как у них одинаковые списки параметров. Чтобы сделать сигнатуру уникальной, объединим их в одну функцию:

Теперь у всех функций разные списки параметров, так что их можно перегрузить под именем move(). Однако этого делать не следует: разные имена несут информацию, без которой программу будет труднее понять. Так, выполняемые данными функциями операции перемещения курсора различны. Например, moveHome() осуществляет специальный вид перемещения в левый верхний угол экрана. Какой из двух приведенных ниже вызовов более понятен пользователю и легче запоминается?

В некоторых случаях не нужно ни перегружать имя функции, ни назначать разные имена: применение подразумеваемых по умолчанию значений аргументов позволяет объединить несколько функций в одну. Например, функции управления курсором

различаются наличием третьего параметра типа char*. Если их реализации похожи и для третьего аргумента можно найти разумное значение по умолчанию, то обе функции можно заменить одной. В данном случае на роль значения по умолчанию подойдет указатель со значением 0:

Применять те или иные возможности следует тогда, когда этого требует логика приложения. Вовсе не обязательно включать перегруженные функции в программу только потому, что они существуют.

9.1.4. Перегрузка и область видимости A

Все перегруженные функции объявляются в одной и той же области видимости. К примеру, локально объявленная функция не перегружает, а просто скрывает глобальную:

Поскольку каждый класс определяет собственную область видимости, функции, являющиеся членами двух разных классов, не перегружают друг друга. (Функции-члены класса описываются в главе 13. Разрешение перегрузки для функций-членов класса рассматривается в главе 15.)
Объявлять такие функции разрешается и внутри пространства имен. С каждым из них также связана отдельная область видимости, так что функции, объявленные в разных пространствах, не перегружают друг друга. Например:

Использование using-объявлений и using-директив помогает сделать члены пространства имен доступными в других областях видимости. Эти механизмы оказывают определенное влияние на объявления перегруженных функций. (Using-объявления и using-директивы рассматривались в разделе 8.6.)

Каким образом using-объявление сказывается на перегрузке функций? Напомним, что оно вводит псевдоним для члена пространства имен в ту область видимости, в которой это объявление встречается. Что делают такие объявления в следующей программе?

Первое using-объявление вводит обе функции libs_R_us::max в глобальную область видимости. Теперь любую из функций max() можно вызвать внутри func(). По типам аргументов определяется, какую именно функцию вызывать. Второе using-объявление – это ошибка: в нем нельзя задавать список параметров. Функция libs_R_us::print() объявляется только так:

Using-объявление всегда делает доступными все перегруженные функции с указанным именем. Такое ограничение гарантирует, что интерфейс пространства имен libs_R_us не будет нарушен. Ясно, что в случае вызова

автор пространства имен ожидает, что будет вызвана функция libs_R_us::print(int). Если разрешить пользователю избирательно включать в область видимости лишь одну из нескольких перегруженных функций, то поведение программы становится непредсказуемым.
Что происходит, если using-объявление вводит в область видимости функцию с уже существующим именем? Эти функции выглядят так, как будто они объявлены прямо в том месте, где встречается using-объявление. Поэтому введенные функции участвуют в процессе разрешения имен всех перегруженных функций, присутствующих в данной области видимости:

Using-объявление добавляет в глобальную область видимости два объявления: для print(int) и для print(double). Они являются псевдонимами в пространстве libs_R_us и включаются в множество перегруженных функций с именем print, где уже находится глобальная print(const string &). При разрешении перегрузки print в fooBar рассматриваются все три функции.
Если using-объявление вводит некоторую функцию в область видимости, в которой уже имеется функция с таким же именем и таким же списком параметров, это считается ошибкой. С помощью using-объявления нельзя задать псевдоним для функции print(int) в пространстве имен libs_R_us, если в глобальной области видимости уже есть print(int). Например:

Мы показали, как связаны using-объявления и перегруженные функции. Теперь рассмотрим особенности применения using-директивы. Using-директива приводит к тому, что члены пространства имен выглядят объявленными вне этого пространства, добавляя их в новую область видимости. Если в этой области уже есть функция с тем же именем, то происходит перегрузка. Например:

Это верно и в том случае, когда есть несколько using-директив. Одноименные функции, являющиеся членами разных пространств, включаются в одно и то множество:

Множество перегруженных функций с именем print в глобальной области видимости включает функции print(int), print(double) и print(long double). Все они рассматриваются в main() при разрешении перегрузки, хотя первоначально были определены в разных пространствах имен.
Итак, повторим, что перегруженные функции находятся в одной и той же области видимости. В частности, они оказываются там в результате применения using-объявлений и using-директив, делающих доступными имена из других областей.

9.1.5. Директива extern «C» и перегруженные функции A

В разделе 7.7 мы видели, что директиву связывания extern «C» можно использовать в программе на C++ для того, чтобы указать, что некоторый объект находится в части, написанной на языке C. Как эта директива влияет на объявления перегруженных функций? Могут ли в одном и том же множестве находиться функции, написанные как на C++, так и на C?
В директиве связывания разрешается задать только одну из множества перегруженных функций. Например, следующая программа некорректна:

Приведенный ниже пример перегруженной функции calc() иллюстрирует типичное применение директивы extern «C»:

Написанная на C функция calc() может быть вызвана как из C, так и из программы на C++. Остальные две функции принимают в качестве параметра класс и, следовательно, их допустимо использовать только в программе на C++. Порядок следования объявлений несуществен.
Директива связывания не имеет значения при решении, какую функцию вызывать; важны только типы параметров. Выбирается та функция, которая лучше всего соответствует типам переданных аргументов:

9.1.6. Указатели на перегруженные функции A

Можно объявить указатель на одну из множества перегруженных функций. Например:

Поскольку функция ff() перегружена, одного инициализатора &ff недостаточно для выбора правильного варианта. Чтобы понять, какая именно функция инициализирует указатель, компилятор ищет в множестве всех перегруженных функций ту, которая имеет тот же тип возвращаемого значения и список параметров, что и функция, на которую ссылается указатель. В нашем случае будет выбрана функция ff(unsigned int).
А что если не найдется функции, в точности соответствующей типу указателя? Тогда компилятор выдаст сообщение об ошибке:

Присваивание работает аналогично. Если значением указателя должен стать адрес перегруженной функции , то для выбора операнда в правой части оператора присваивания используется тип указателя на функцию. И если компилятор не находит функции, в точности соответствующей нужному типу, он выдает сообщение об ошибке. Таким образом, преобразование типов между указателями на функции никогда не производится.

9.1.7. Безопасное связывание A

При использовании перегрузки складывается впечатление, что в программе можно иметь несколько одноименных функций с разными списками параметров. Однако это лексическое удобство существует только на уровне исходного текста. В большинстве систем компиляции программы, обрабатывающие этот текст для получения исполняемого кода, требуют, чтобы все имена были различны. Редакторы связей, как правило, разрешают внешние ссылки лексически. Если такой редактор встречает имя print два или более раз, он не может различить их путем анализа типов (к этому моменту информация о типах обычно уже потеряна). Поэтому он просто печатает сообщение о повторно определенном символе print и завершает работу.
Чтобы разрешить эту проблему, имя функции вместе с ее списком параметров декорируется так, чтобы получилось уникальное внутреннее имя. Вызываемые после компилятора программы видят только это внутреннее имя. Как именно производится такое преобразование имен, зависит от реализации. Общая идея заключается в том, чтобы представить число и типы параметров в виде строки символов и дописать ее к имени функции.
Как было сказано в разделе 8.2, такое кодирование гарантирует, в частности, что два объявления одноименных функций с разными списками параметров, находящиеся в разных файлах, не воспринимаются редактором связей как объявления одной и той же функции. Поскольку этот способ помогает различить перегруженные функции на фазе редактирования связей, мы говорим о безопасном связывании.
Декорирование имен не применяется к функциям, объявленным с помощью директивы extern «C», так как лишь одна из множества перегруженных функций может быть написана на чистом С. Две функции с различными списками параметров, объявленные как extern «C», редактор связей воспринимает как один и тот же символ.

Упражнение 9.1

Зачем может понадобиться объявлять перегруженные функции?

Упражнение 9.2

Как нужно объявить перегруженные варианты функции error(), чтобы были корректны следующие вызовы:

Упражнение 9.3

Объясните, к какому эффекту приводит второе объявление в каждом из приведенных примеров:

Упражнение 9.4

Какая из следующих инициализаций приводит к ошибке? Почему?

9.2. Три шага разрешения перегрузки

Разрешением перегрузки функции называется процесс выбора той функции из множества перегруженных, которую следует вызвать. Этот процесс основывается на указанных при вызове аргументах. Рассмотрим пример:

Здесь в ходе процесса разрешения перегрузки в зависимости от типа T определяется, будет ли при обработке выражения f(t1,t2) вызвана функция f(int,int) или f(float,float) или зафиксируется ошибка.
Разрешение перегрузки функции – один и самых сложных аспектов языка C++. Пытаясь разобраться во всех деталях, начинающие программисты столкнутся с серьезными трудностями. Поэтому в данном разделе мы представим лишь краткий обзор того, как происходит разрешение перегрузки, чтобы у вас составилось хоть какое-то впечатление об этом процессе. Для тех, кто хочет узнать больше, в следующих двух разделах приводится более подробное описание.
Процесс разрешения перегрузки функции состоит из трех шагов, которые мы покажем на следующем примере:

При разрешении перегрузки функции выполняются следующие шаги:

  1. Выделяется множество перегруженных функций для данного вызова, а также свойства списка аргументов, переданных функции.
  2. Выбираются те из перегруженных функций, которые могут быть вызваны с данными аргументами, с учетом их количества и типов.
  3. Находится функция, которая лучше всего соответствует вызову.

Рассмотрим последовательно каждый пункт.
На первом шаге необходимо идентифицировать множество перегруженных функций, которые будут рассматриваться при данном вызове. Вошедшие в это множество функции называются кандидатами. Функция-кандидат – это функция с тем же именем, что и вызванная, причем ее объявление видимо в точке вызова. В нашем примере есть четыре таких кандидата: f(), f(int), f(double, double) и f(char*, char*).
После этого идентифицируются свойства списка переданных аргументов, т.е. их количество и типы. В нашем примере список состоит из двух аргументов типа double.
На втором шаге среди множества кандидатов отбираются устоявшие (viable) – такие, которые могут быть вызваны с данными аргументами, Устоявшая функция либо имеет столько же формальных параметров, сколько фактических аргументов передано вызванной функции, либо больше, но тогда для каждого дополнительного параметра должно быть задано значение по умолчанию. Чтобы функция считалась устоявшей, для любого фактического аргумента, переданного при вызове, обязано существовать преобразование к типу формального параметра, указанного в объявлении.

В нашем примере есть две устоявших функции, которые могут быть вызваны с приведенными аргументами:

  • функция f(int) устояла, потому что у нее есть всего один параметр и существует преобразование фактического аргумента типа double к формальному параметру типа int;
  • функция f(double,double) устояла, потому что для второго аргумента есть значение по умолчанию, а первый формальный параметр имеет тип double, что в точности соответствует типу фактического аргумента.

Если после второго шага не нашлось устоявших функций, то вызов считается ошибочным. В таких случаях мы говорим, что имеет место отсутствие соответствия.
Третий шаг заключается в выборе функции, лучше всего отвечающей контексту вызова. Такая функция называется наилучшей из устоявших (или наиболее подходящей). На этом шаге производится ранжирование преобразований, использованных для приведения типов фактических аргументов к типам формальных параметров устоявшей функции. Наиболее подходящей считается функция, для которой выполняются следующие условия:
преобразования, примененные к фактическим аргументам, не хуже преобразований, необходимых для вызова любой другой устоявшей функции;
для некоторых аргументов примененные преобразования лучше, чем преобразования, необходимые для приведения тех же аргументов в вызове других устоявших функций.
Преобразования типов и их ранжирование более подробно обсуждаются в разделе 9.3. Здесь мы лишь кратко рассмотрим ранжирование преобразований для нашего примера. Для устоявшей функции f(int) должно быть применено приведение фактического аргумента типа double к типу int, относящееся к числу стандартных. Для устоявшей функции f(double,double) тип фактического аргумента double в точности соответствует типу формального параметра. Поскольку точное соответствие лучше стандартного преобразования (отсутствие преобразования всегда лучше, чем его наличие), то наиболее подходящей функцией для данного вызова считается f(double,double).
Если на третьем шаге не удается отыскать единственную лучшую из устоявших функцию, иными словами, нет такой устоявшей функции, которая подходила бы больше всех остальных, то вызов считается неоднозначным, т.е. ошибочным.
(Более подробно все шаги разрешения перегрузки функции обсуждаются в разделе 9.4. Процесс разрешения используется также при вызовах перегруженной функции-члена класса и перегруженного оператора. В разделе 15.10 рассматриваются правила разрешения перегрузки, применяемые к функциям-членам класса, а в разделе 15.11 – правила для перегруженных операторов. При разрешении перегрузки следует также принимать во внимание функции, конкретизированные из шаблонов. В разделе 10.8 обсуждается, как шаблоны влияют на такое разрешение.)

Упражнение 9.5


Что происходит на последнем (третьем) шаге процесса разрешения перегрузки функции?

9.3. Преобразования типов аргументов A

На втором шаге процесса разрешения перегрузки функции компилятор идентифицирует и ранжирует преобразования, которые следует применить к каждому фактическому аргументу вызванной функции для приведения его к типу соответствующего формального параметра любой из устоявших функций. Ранжирование может дать один из трех возможных результатов:

  • точное соответствие. Тип фактического аргумента точно соответствует типу формального параметра. Например, если в множестве перегруженных функций print() есть такие: то каждый из следующих трех вызовов дает точное соответствие:
    unsigned int a;
  • соответствие с преобразованием типа. Тип фактического аргумента не соответствует типу формального параметра, но может быть преобразован в него:
  • отсутствие соответствия. Тип фактического аргумента не может быть приведен к типу формального параметра в объявлении функции, поскольку необходимого преобразования не существует. Для каждого из следующих двух вызовов функции print() соответствия нет:

Для установления точного соответствия тип фактического аргумента необязательно должен совпадать с типом формального параметра. К аргументу могут быть применены некоторые тривиальные преобразования, а именно:

  • преобразование l-значения в r-значение;
  • преобразование массива в указатель;
  • преобразование функции в указатель;
  • преобразования спецификаторов.

(Подробнее они рассмотрены ниже.)Категория соответствия с преобразованием типа является наиболее сложной. Необходимо рассмотреть несколько видов такого приведения: расширение типов (promotions), стандартные преобразования и определенные пользователем преобразования. (Расширения типов и стандартные преобразования изучаются в этой главе. Определенные пользователем преобразования будут представлены позднее, после детального рассмотрения классов; они выполняются конвертером, функцией-членом, которая позволяет определить в классе собственный набор “стандартных” трансформаций. В главе 15 мы познакомимся с такими конвертерами и с тем, как они влияют на разрешение перегрузки функций.)
При выборе лучшей из устоявших функций для данного вызова компилятор ищет функцию, для которой применяемые к фактическим аргументам преобразования являются “наилучшими”. Преобразования типов ранжируются следующим образом: точное соответствие лучше расширения типа, расширение типа лучше стандартного преобразования, а оно, в свою очередь, лучше определенного пользователем преобразования. Мы еще вернемся к ранжированию в разделе 9.4, а пока на простых примерах покажем, как оно помогает выбрать наиболее подходящую функцию.

9.3.1. Подробнее о точном соответствии

Самый простой случай возникает тогда, когда типы фактических аргументов совпадают с типами формальных параметров. Например, есть две показанные ниже перегруженные функции max(). Тогда каждый из вызовов max() точно соответствует одному из объявлений:

Мастер Йода рекомендует:  Как вытащить носки одинакового цвета, не заглядывая в комод

Перечислимый тип точно соответствует только определенным в нем элементам перечисления, а также объектам, которые объявлены как принадлежащие к этому типу:

Выше уже упоминалось, что фактический аргумент может точно соответствовать формальному параметру, даже если для приведения их типов необходимо некоторое тривиальное преобразование, первое из которых – преобразование l-значения в r-значение. Под l-значением понимается объект, удовлетворяющий следующим условиям:

  • можно получить адрес объекта;
  • можно получить значение объекта;
  • это значение легко модифицировать (если только в объявлении объекта нет спецификатора const).

Напротив, r-значение – это выражение, значение которого вычисляется, или выражение, обозначающее временный объект, для которого нельзя получить адрес и значение которого нельзя модифицировать. Вот простой пример:

В первом операторе присваивания переменная lval – это l-значение, а литерал 5 – r-значение. Во втором операторе присваивания res – это l-значение, а временный объект, в котором хранится результат, возвращаемый функцией calc(), – это r-значение.
В некоторых ситуациях в контексте, где ожидается значение, можно использовать выражение, представляющее собой l-значение:

Здесь obj1 и obj2 – это l-значения. Однако для выполнения сложения в функции main() из переменных obj1 и obj2 извлекаются их значения. Действие, состоящее в извлечении значения объекта, представленного выражением вида l-значение, называется преобразованием l-значения в r-значение.
Когда функция ожидает аргумент, переданный по значению, то в случае, если аргумент является l-значением, выполняется его преобразование в r-значение:

Так как аргумент в вызове print(color) передается по значению, то производится преобразование l-значения в r-значение для извлечения значения color и передачи его в функцию с прототипом print(string). Однако несмотря на то, что такое приведение имело место, считается, что фактический аргумент color точно соответствует объявлению print(string).
При вызове функций не всегда требуется применять к аргументам подобное преобразование. Ссылка представляет собой l-значение; если у функции есть параметр-ссылка, то при вызове функция получает l-значение. Поэтому к фактическому аргументу, которому соответствует формальный параметр-ссылка, описанное преобразование не применяется. Например, пусть объявлена такая функция:

В вызове ниже li – это l-значение, представляющее объект list , передаваемый функции print():

Сопоставление li с параметром-ссылкой считается точным соответствием.
Второе преобразование, при котором все же фиксируется точное соответствие, – это преобразование массива в указатель. Как уже отмечалось в разделе 7.3, параметр функции никогда не имеет тип массива, трансформируясь вместо этого в указатель на его первый элемент. Аналогично фактический аргумент типа массива из NT (где N – число элементов в массиве, а T – тип каждого элемента) всегда приводится к типу указателя на T. Такое преобразование типа фактического аргумента и называется преобразованием массива в указатель. Несмотря на это, считается, что фактический аргумент точно соответствует формальному параметру типа “указатель на T”. Например:

Перед вызовом функции putValues() массив преобразуется в указатель, в результате чего фактический аргумент ai (массив из трех целых) приводится к указателю на int. Хотя формальным параметром функции putValues() является указатель и фактический аргумент при вызове преобразован, между ними устанавливается точное соответствие.
При установлении точного соответствия допустимо также преобразование функции в указатель. (Оно упоминалось в разделе 7.9.) Как и параметр-массив, параметр-функция становится указателем на функцию. Фактический аргумент типа “функция” также автоматически приводится к типу указателя на функцию. Такое преобразование типа фактического аргумента и называется преобразованием функции в указатель. Хотя трансформация производится, считается, что фактический аргумент точно соответствует формальному параметру. Например:

Перед вызовом sort() применяется преобразование функции в указатель, которое приводит аргумент lexicoCompare от типа “функция” к типу “указатель на функцию”. Хотя формальным параметром функции является указатель, а фактическим – имя функции и, следовательно, было произведено преобразование функции в указатель, считается, что фактический аргумент точно третьему формальному параметру функции sort().
Последнее из перечисленных выше – это преобразование спецификаторов. Оно относится только к указателям и заключается в добавлении спецификаторов const или volatile (или обоих) к типу, который адресует данный указатель:

Перед вызовом функции is_equal() фактические аргументы pi и parm преобразуются из типа “указатель на int” в тип “указатель на const int”. Эта трансформация заключается в добавлении спецификатора const к адресуемому типу, поэтому относится к категории преобразований спецификаторов. Несмотря на то, что функция ожидает получить два указателя на const int, а фактические аргументы являются указателями на int, считается, что точное соответствие между формальными и фактическими параметрами функции is_equal() установлено.
Преобразование спецификаторов применимо только к типу, который адресует указатель. Оно не употребляется в случае, когда формальный параметр имеет спецификатор const или volatile, а фактический аргумент – нет.

Хотя формальный параметр функции takeCI() имеет тип const int, а вызывается она с аргументом ii типа int, преобразование спецификаторов не производится: есть точное соответствие между фактическим аргументом и формальным параметром.
Все сказанное верно и для случая, когда аргумент является указателем, а спецификаторы const или volatile относятся к этому указателю:

Спецификатор const при формальном параметре функции init() относится к самому указателю, а не к типу, который он адресует. Поэтому компилятор при анализе преобразований, которые должны быть применены к фактическому аргументу, не учитывает этот спецификатор. К аргументу pi не применяется преобразование спецификатора: считается, что этот аргумент и формальный параметр точно соответствуют друг другу.
Первые три из рассмотренных преобразований (l-значения в r-значение, массива в указатель и функции в указатель) часто называют трансформациями l-значений. (В разделе 9.4 мы увидим, что хотя и трансформации l-значений, и преобразования спецификаторов относятся к категории преобразований, не нарушающих точного соответствия, его степень считается выше в случае, когда необходима лишь первая трансформация. В следующем разделе мы поговорим об этом несколько подробнее.)
Точное соответствие можно установить принудительно, воспользовавшись явным приведением типов. Например, если есть две перегруженные функции:

будет точно соответствовать ff(int), хотя литерал 0xffbc записан в виде шестнадцатеричной константы. Программист может заставить компилятор вызвать функцию ff(void *), если явно выполнит операцию приведения типа:

Если к фактическому аргументу применяется такое приведение, то он приобретает тип, в который преобразуется. Явные приведения типов помогают в управлении процессом разрешения перегрузки. Например, если при разрешении перегрузки получается неоднозначный результат (фактические аргументы одинаково хорошо соответствуют двум или более устоявшим функциям), то для устранения неоднозначности можно применить явное приведение типа, заставив компилятор выбрать конкретную функцию.

9.3.2. Подробнее о расширении типов

Под расширением типа понимается одно из следующих преобразований:

  • фактический аргумент типа char, unsigned char или short расширяется до типа int. Фактический аргумент типа unsigned short расширяется до типа int, если машинный размер int больше, чем размер short, и до типа unsigned int в противном случае;
  • аргумент типа float расширяется до типа double;
  • аргумент перечислимого типа расширяется до первого из следующих типов, который способен представить все значения элементов перечисления: int, unsigned int, long, unsigned long;
  • аргумент типа bool расширяется до типа int.

Подобное расширение применяется, когда тип фактического аргумента совпадает с одним из только что перечисленных типов, а формальный параметр относится к соответствующему расширенному типу:

Символьный литерал имеет тип char. Он расширяется до int. Поскольку расширенный тип соответствует типу формального параметра функции manip(), мы говорим, что ее вызов требует расширения типа аргумента.
Рассмотрим следующий пример:

Для аппаратной платформы, на которой unsigned char занимает один байт памяти, а int – четыре байта, расширение преобразует unsigned char в int, так как с его помощью можно представить все значения типа unsigned char. Для такой машинной архитектуры из приведенного в примере множества перегруженных функций наилучшее соответствие аргументу типа unsigned char обеспечивает print(int). Для двух других функций установление соответствия требует стандартного приведения.
Следующий пример иллюстрирует расширение фактического аргумента перечислимого типа:

Иногда расширение перечислений преподносит сюрпризы. Компиляторы часто выбирают представление перечисления в зависимости от значений его элементов. Предположим, что в вышеупомянутой архитектуре (один байт для char и четыре байта для int) определено такое перечисление:

Поскольку есть всего три элемента: a1, b1 и c1 со значениями 0, 1 и 2 соответственно – и поскольку все эти значения можно представить типом char, то компилятор, как правило, и выбирает char для представления типа e1. Рассмотрим, однако, перечисление e2 со следующим множеством элементов:

Так как одна из констант имеет значение 0x80000000, то компилятор обязан выбрать для представления e2 такой тип, который достаточен для хранения значения 0x80000000, то есть unsigned int.
Итак, хотя и e1, и e2 являются перечислениями, их представления различаются. Из-за этого e1 и e2 расширяются до разных типов:

При первом обращении к format() фактический аргумент расширяется до типа int, так как для представления типа e1 используется char, и, следовательно, вызывается перегруженная функция format(int). При втором обращении тип фактического аргумента e2 представлен типом unsigned int и аргумент расширяется до unsigned int, из-за чего вызывается перегруженная функция format(unsigned int). Поэтому следует помнить, что поведение двух перечислений по отношению к процессу разрешения перегрузки может быть различным и зависеть от значений элементов, определяющих, как происходит расширение типа.

9.3.3. Подробнее о стандартном преобразовании

Имеется пять видов стандартных преобразований, а именно:

  1. преобразования целых типов: приведение от целого типа или перечисления к любому другому целому типу (исключая трансформации, которые выше были отнесены к категории расширения типов);
  2. преобразования типов с плавающей точкой: приведение от любого типа с плавающей точкой к любому другому типу с плавающей точкой (исключая трансформации, которые выше были отнесены к категории расширения типов);
  3. преобразования между целым типом и типом с плавающей точкой: приведение от любого типа с плавающей точкой к любому целому типу или наоборот;
  4. преобразования указателей: приведение целого значения 0 к типу указателя или трансформация указателя любого типа в тип void*;
  5. преобразования в тип bool: приведение от любого целого типа, типа с плавающей точкой, перечислимого типа или указательного типа к типу bool.

Вот несколько примеров:

Преобразования, относящиеся к группам 1, 2 и 3, потенциально опасны, так как целевой тип может и не обеспечивать представления всех значений исходного. Например, с помощью float нельзя адекватно представить все значения типа int. Именно по этой причине трансформации, входящие в эти группы, отнесены к категории стандартных преобразований, а не расширений типов.

При вызове функции calc() применяется стандартное преобразование из целого типа int в тип с плавающей точкой float. В зависимости от значения переменной i может оказаться, что его нельзя сохранить в типе float без потери точности.
Предполагается, что все стандартные изменения требуют одного объема работы. Например, преобразование из char в unsigned char не более приоритетно, чем из char в double. Близость типов не принимается во внимание. Если две устоявших функции требуют для установления соответствия стандартной трансформации фактического аргумента, то вызов считается неоднозначным и помечается компилятором как ошибка. Например, если даны две перегруженные функции:

то следующий вызов неоднозначен:

Константа 3.14 имеет тип double. С помощью того или иного стандартного преобразования соответствие может быть установлено с любой из перегруженных функций. Поскольку есть две трансформации, приводящие к цели, вызов считается неоднозначным. Ни одно преобразование не имеет преимущества над другим. Программист может разрешить неоднозначность либо путем явного приведения типа:

либо используя суффикс, обозначающий, что константа принадлежит к типу float:

Вот еще несколько примеров неоднозначных вызовов, которые помечаются как ошибки, поскольку соответствуют нескольким перегруженным функциям:

Стандартные преобразования указателей иногда противоречат интуиции. В частности, значение 0 приводится к указателю на любой тип; полученный таким образом указатель называется нулевым. Значение 0 может быть представлено как константное выражение целого типа:

Константное выражение 0L (значение 0 типа long int) и константное выражение 0x00 (шестнадцатеричное целое значение 0) имеют целый тип и потому могут быть преобразованы в нулевой указатель типа int*.
Но поскольку перечисления не относятся к целым типам, элемент, равный 0, не приводим к типу указателя:

Вызов функции set() является ошибкой, так как не существует преобразования между значением zr элемента перечисления и формальным параметром типа int*, хотя zr равно 0.
Следует отметить, что константное выражение 0 имеет тип int. Для его приведения к типу указателя требуется стандартное преобразование. Если в множестве перегруженных функций есть функция с формальным параметром типа int, то именно в ее пользу будет разрешена перегрузка в случае, когда фактический аргумент равен 0:


При вызове print(int) имеет место точное соответствие, тогда как для вызова print(void*) необходимо приведение значения 0 к типу указателя. Поскольку соответствие лучше преобразования, для разрешения этого вызова выбирается функция print(int). Обращение к set() неоднозначно, так как 0 соответствует формальным параметрам обеих перегруженных функций за счет применения стандартной трансформации. Раз обе функции одинаково хороши, фиксируется неоднозначность.
Последнее из возможных преобразований указателя позволяет привести указатель любого типа к типу void*, поскольку void* – это родовой указатель на любой тип данных. Вот несколько примеров:

Только указатели на типы данных могут быть приведены к типу void* с помощью стандартного преобразования, с указателями на функции так поступать нельзя:

9.3.4. Ссылки

Фактический аргумент или формальный параметр функции могут быть ссылками. Как это влияет на правила преобразования типов?
Рассмотрим, что происходит, когда ссылкой является фактический аргумент. Его тип никогда не бывает ссылочным. Аргумент-ссылка трактуется как l-значение, тип которого совпадает с типом соответствующего объекта:

Фактический аргумент в обоих вызовах имеет тип int. Использование ссылки для его передачи во втором вызове не влияет на сам тип аргумента.
Стандартные преобразования и расширения типов, рассматриваемые компилятором, одинаковы для случаев, когда фактический аргумент является ссылкой на тип T и когда он сам имеет такой тип. Например:

А как влияет на преобразования, применяемые к фактическому аргументу, формальный параметр-ссылка? Сопоставление дает следующие результаты:

  • фактический аргумент подходит в качестве инициализатора параметра-ссылки. В таком случае мы говорим, что между ними есть точное соответствие:
  • фактический аргумент не может инициализировать параметр-ссылку. В такой ситуации точного соответствия нет, и аргумент нельзя использовать для вызова функции. Например:
  • Вызов функции frd() является ошибкой. Фактический аргумент имеет тип int и должен быть преобразован в тип double, чтобы соответствовать формальному параметру-ссылке. Результатом такой трансформации является временная переменная. Поскольку ссылка не имеет спецификатора const, то для ее инициализации такие переменные использовать нельзя.
    Вот еще один пример, в котором между формальным параметром-ссылкой и фактическим аргументом нет соответствия: Вызов функции takeB() – ошибка. Фактический аргумент – это возвращаемое значение, т.е. временная переменная, которая не может быть использована для инициализации ссылки без спецификатора const.
    В обоих случаях мы видим, что если формальный параметр-ссылка имеет спецификатор const, то между ним и фактическим аргументом может быть установлено точное соответствие.

Следует отметить, что и преобразование l-значения в r-значение, и инициализация ссылки считаются точными соответствиями. В данном примере первый вызов функции приводит к ошибке:

Объект iobj – это аргумент, для которого может быть установлено соответствие с обеими функциями print(), то есть вызов неоднозначен. То же относится и к следующей строке, где ссылка ri обозначает объект, соответствующий обеим функциям print(). С третьим вызовом, однако, все в порядке. Для него print(int&) не является устоявшей. Целая константа – это r-значение, так что она не может инициализировать параметр-ссылку. Единственной устоявшей функцией для вызова print(86) является print(int), поэтому она и выбирается при разрешении перегрузки.
Короче говоря, если формальный параметр представляет собой ссылку, то для фактического аргумента точное соответствие устанавливается, если он может инициализировать ссылку, и не устанавливается в противном случае.

Упражнение 9.6

Назовите два тривиальных преобразования, допустимых при установлении точного соответствия.

Упражнение 9.7

Каков ранг каждого из преобразований аргументов в следующих вызовах функций:

Упражнение 9.8

Какие из данных вызовов ошибочны из-за того, что не существует преобразования между типом фактического аргумента и формального параметра:

9.4. Детали разрешения перегрузки функций

В разделе 9.2 мы уже упоминали, что процесс разрешения перегрузки функций состоит из трех шагов:

  1. Установить множество функций-кандидатов для разрешения данного вызова, а также свойства списка фактических аргументов.
  2. Отобрать из множества кандидатов устоявшие функции – те, которые могут быть вызваны с данным списком фактических аргументов при учете их числа и типов.
  3. Выбрать функцию, лучше всего соответствующую вызову, подвергнув ранжированию преобразования, которые необходимо применить к фактическим аргументам, чтобы привести их в соответствие с формальными параметрами устоявшей функции.

Теперь мы готовы к тому, чтобы изучить эти шаги более детально.

9.4.1. Функции-кандидаты

Функцией-кандидатом называется функция, имеющая то же имя, что и вызванная. Кандидаты отыскиваются двумя способами:

  • объявление функции видимо в точке вызова. В следующем примере
    все четыре функции f() удовлетворяют этому условию. Поэтому множество кандидатов содержит четыре элемента;
  • если тип фактического аргумента объявлен внутри некоторого пространства имен, то функции-члены этого пространства, имеющие то же имя, что и вызванная функция, добавляются в множество кандидатов:

Таким образом, совокупность кандидатов является объединением множества функций, видимых в точке вызова, и множества функций, объявленных в том же пространстве имен, к которому принадлежат типы фактических аргументов.
При идентификации множества перегруженных функций, видимых в точке вызова, применимы уже рассмотренные ранее правила.
Функция, объявленная во вложенной области видимости, скрывает, а не перегружает одноименную функцию во внешней области. В такой ситуации кандидатами будут только функции из во вложенной области, т.е. такие, которые не скрыты при вызове. В следующем примере функциями-кандидатами, видимыми в точке вызова, являются format(double) и format(char*):

Так как format(int), объявленная в глобальной области видимости, скрыта, она не включается в множество функций-кандидатов.
Кандидаты могут быть введены с помощью using-объявлений, видимых в точке вызова:

Функции max(), определенные в пространстве имен libs_R_us, невидимы в точке вызова. Единственной видимой является функция max() из глобальной области; только она входит в множество функций-кандидатов и вызывается при каждом из трех обращений к func(). Мы можем воспользоваться using-объявлением, чтобы сделать видимыми функции max() из пространства имен libs_R_us. Куда поместить using-объявление? Если включить его в глобальную область видимости:

то функции max() из libs_R_us добавляются в множество перегруженных функций, которое уже содержит max(), объявленную в глобальной области. Теперь все три функции видны внутри func() и становятся кандидатами. В этой ситуации вызовы func() разрешаются следующим образом:

Но что будет, если мы введем using-объявление в локальную область видимости функции func(), как показано в данном примере?

Какие из функций max() будут включены в множество кандидатов? Напомним, что using-объявления вкладываются друг в друга. При наличии такого объявления в локальной области глобальная функция max(char, char) оказывается скрытой, так что в точке вызова видны только

Они и являются кандидатами. Теперь вызовы func() разрешаются следующим образом:

Using-директивы также оказывают влияние на состав множества функций-кандидатов. Предположим, мы решили их использовать, чтобы сделать функции max() из пространства имен libs_R_us видимыми в func(). Если разместить следующую using-директиву в глобальной области видимости, то множество функций-кандидатов будет состоять из глобальной функции max(char, char) и функций max(int, int) и max(double, double), объявленных в libs_R_us:

Что будет, если поместить using-директиву в локальную область видимости, как в следующем примере?

Какие из функций max() окажутся среди кандидатов? Напомним, что using-директива делает члены пространства имен видимыми, словно они были объявлены вне этого пространства, в той точке, где такая директива помещается. В нашем примере члены libs_R_us видимы в локальной области функции func(), как будто они объявлены вне пространства – в глобальной области. Отсюда следует, что множество перегруженных функций, видимых внутри func(), то же, что и раньше, т.е. включает в себя

В локальной или глобальной области видимости появляется using-директива, на разрешение вызовов функции func() не влияет:

Итак, множество кандидатов состоит из функций, видимых в точке вызова, включая и те, которые введены using-объявлениями и using-директивами, а также из функций, объявленных в пространствах имен, ассоциированных с типами фактических аргументов. Например:

Кандидатами для print(mObj) являются введенные using-объявлением внутри display() функции basicLib::print(int) и basicLib::print(double), поскольку они видимы в точке вызова. Так как фактический аргумент функции имеет тип matrixLib::matrix, то функция print(), объявленная в пространстве имен matrixLib, также будет кандидатом. Каковы функции-кандидаты для print(87)? Только basicLib::print(int) и basicLib::print(double), видимые в точке вызова. Поскольку аргумент имеет тип int, дополнительное пространство имен в поисках других кандидатов не рассматривается.

9.4.2. Устоявшие функции

Устоявшая функция относится к числу кандидатов. В списке ее формальных параметров либо то же самое число элементов, что и в списке фактических аргументов вызванной функции, либо больше. В последнем случае для дополнительных параметров задаются значения по умолчанию, иначе функцию нельзя будет вызвать с данным числом аргументов. Чтобы функция считалась устоявшей, должно существовать преобразование каждого фактического аргумента в тип соответствующего формального параметра. (Такие преобразования были рассмотрены в разделе 9.3.)
В следующем примере для вызова f(5.6) есть две устоявшие функции: f(int) и f(double).

Функция f(int) устояла, так как она имеет всего один формальный параметр, что соответствует числу фактических аргументов в вызове. Кроме того, существует стандартное преобразование аргумента типа double в int. Функция f(double) также устояла; она тоже имеет один параметр типа double, и он точно соответствует фактическому аргументу. Функции-кандидаты f() и f(char*, char*) исключены из списка устоявших, так как они не могут быть вызваны с одним аргументом.
В следующем примере единственной устоявшей функцией для вызова format(3) является format(double). Хотя кандидата format(char*) можно вызывать с одним аргументом, не существует преобразования из типа фактического аргумента int в тип формального параметра char*, а следовательно, функция не может считаться устоявшей.

В следующем примере все три функции-кандидата оказываются устоявшими для вызова max() внутри func(). Все они могут быть вызваны с двумя аргументами. Поскольку фактические аргументы имеют тип int, они точно соответствуют формальным параметрам функции libs_R_us::max(int, int) и могут быть приведены к типам параметров функции libs_R_us::max(double, double) с помощью трансформации целых в плавающие, а также к типам параметров функции libs_R_us::max(char, char) посредством преобразования целых типов.

Обратите внимание, что функция-кандидат с несколькими параметрами исключается из числа устоявших, как только выясняется, что один из фактических аргументов не может быть приведен к типу соответствующего формального параметра, пусть даже для всех остальных аргументов такое преобразование существует. В следующем примере функция min(char *, int) исключается из множества устоявших, поскольку нет возможности трансформации типа первого аргумента int в тип соответствующего параметра char *. И это происходит несмотря на то, что второй аргумент точно соответствует второму параметру.

Если после исключения из множества кандидатов всех функций с несоответствующим числом параметров и тех, для параметров которых не оказалось подходящего преобразования, не осталось устоявших, то обработка вызова функции заканчивается ошибкой компиляции. В таком случае говорят, что соответствия не найдено.

9.4.3. Наилучшая из устоявших функция

Наилучшей считается та из устоявших функций, формальные параметры которой наиболее точно соответствуют типам фактических аргументов. Для любой такой функции преобразования типов, применяемые к каждому аргументу, ранжируются для определения степени его соответствия параметру. (В разделе 6.2 описаны поддерживаемые преобразования типов.) Наилучшей из устоявших называют функцию, для которой одновременно выполняются два условия:

  • преобразования, примененные к аргументам, не хуже преобразований, необходимых для вызова любой другой устоявшей функции;
  • хотя бы для одного аргумента примененное преобразование лучше, чем для того же аргумента в любой другой устоявшей функции.

Может оказаться так, что для приведения фактического аргумента к типу соответствующего формального параметра нужно выполнить несколько преобразований. Так, в следующем примере

для приведения аргумента arr от типа “массив из трех int” к типу “указатель на const int” применяется последовательность преобразований:

  1. Преобразование массива в указатель, которое трансформирует массив из трех int в указатель на int.
  2. Преобразование спецификатора, которое трансформирует указатель на int в указатель на const int.


Поэтому было бы более правильно говорить, что для приведения фактического аргумента к типу формального параметра устоявшей функции требуется последовательность преобразований. Поскольку применяется не одна, а несколько трансформаций, то на третьем шаге процесса разрешения перегрузки функции на самом деле ранжируются последовательности преобразований.
Рангом такой последовательности считается ранг самой плохой из входящих в нее трансформаций. Как объяснялось в разделе 9.2, преобразования типов ранжируются следующим образом: точное соответствие лучше расширения типа, а расширение типа лучше стандартного преобразования. В предыдущем примере оба изменения имеют ранг точного соответствия. Поэтому и у всей последовательности такой же ранг.
Такая совокупность состоит из нескольких преобразований, применяемых в указанном порядке:

Термин преобразование l-значения относится к первым трем трансформациям из категории точных соответствий, рассмотренных в разделе 9.2: преобразование l-значения в r-значение, преобразование массива в указатель и преобразование функции в указатель. Последовательность трансформаций состоит из нуля или одного преобразования l-значения, за которым следует нуль или одно расширение типа или стандартное преобразование, и наконец нуль или одно преобразование спецификаторов. Для приведения фактического аргумента к типу формального параметра может быть применено только одна трансформация каждого вида.

Описанная последовательность называется последовательностью стандартных преобразований. Существует также последовательность определенных пользователем преобразований, которая связана с функцией-конвертером, являющейся членом класса. (Конвертеры и последовательности определенных пользователем преобразований рассматриваются в главе 15.)

Каковы последовательности изменений фактических аргументов в следующем примере?

Аргументы в вызове функции max() имеют тип char. Последовательность преобразований аргументов при вызове функции libs_R_us::max(int,int) следующая:

1a. Так как аргументы передаются по значению, то с помощью преобразования l-значения в r-значение извлекаются значения аргументов c1 и c2.

2a. С помощью расширения типа аргументы трансформируются из char в int.
Последовательность преобразований аргументов при вызове функции libs_R_us::max(double,double) следующая:
1b. С помощью преобразования l-значения в r-значение извлекаются значения аргументов c1 и c2.

2b. Стандартное преобразование между целым и плавающим типом приводит аргументы от типа char к типу double.

Ранг первой последовательности – расширение типа (самое худшее из примененных изменений), тогда как ранг второй – стандартное преобразование. Так как расширение типа лучше, чем преобразование, то в качестве наилучшей из устоявших для данного вызова выбирается функция libs_R_us::max(int,int).
Если ранжирование последовательностей преобразований аргументов не может выявить единственной устоявшей функции, то вызов считается неоднозначным. В данном примере для обоих вызовов calc() требуется такая последовательность:

  1. Преобразование l-значения в r-значение для извлечения значений аргументов i и j.
  2. Стандартное преобразование для приведения типов фактических аргументов к типам соответствующих формальных параметров.

Поскольку нельзя сказать, какая из этих последовательностей лучше другой, вызов неоднозначен:

Преобразование спецификаторов (добавление спецификатора const или volatile к типу, который адресует указатель) имеет ранг точного соответствия. Однако, если две последовательности трансформаций отличаются только тем, что в конце одной из них есть дополнительное преобразование спецификаторов, то последовательность без него считается лучше. Например:

Последовательность стандартных преобразований, примененная к фактическому аргументу для первой функции-кандидата reset(int*), – это точное соответствие, требуется лишь переход от l-значения к r-значению, чтобы извлечь значение аргумента. Для второй функции-кандидата reset(const int *) также применяется трансформация l-значения в r-значение, но за ней следует еще и преобразование спецификаторов для приведения результирующего значения от типа “указатель на int” к типу “указатель на const int”. Обе последовательности представляют собой точное соответствие, но неоднозначности при этом не возникает. Так как вторая последовательность отличается от первой наличием трансформации спецификаторов в конце, то последовательность без такого преобразования считается лучшей. Поэтому наилучшей из устоявших функций будет reset(int*).
Вот еще пример, в котором приведение спецификаторов влияет на то, какая последовательность будет выбрана:

int extract( void * );
int extract( const void * );

int main() <
extract( pi ); // выбирается extract( void * )
return 0;
>

Здесь для вызова есть две устоявших функции: extract(void*) и extract(const void*). Последовательность преобразований для функции extract(void*) состоит из трансформации l-значения в r-значение для извлечения значения аргумента, сопровождаемого стандартным преобразованием указателя: из указателя на int в указатель на void. Для функции extract(const void*) такая последовательность отличается от первой дополнительным преобразованием спецификаторов для приведения типа результата от указателя на void к указателю на const void. Поскольку последовательности различаются лишь этой трансформацией, то первая выбирается как более подходящая и, следовательно, наилучшей из устоявших будет функция extract(const void*).
Спецификаторы const и volatile влияют также на ранжирование инициализации параметров-ссылок. Если две такие инициализации отличаются только добавлением спецификатора const и volatile, то инициализация без дополнительной спецификации считается лучшей при разрешении перегрузки:

Мастер Йода рекомендует:  12 основных плагинов для разработчиков WordPress

В первом вызове инициализация ссылок для вызова любой функции является точным соответствием. Но этот вызов все же не будет неоднозначным. Так как обе инициализации одинаковы во всем, кроме наличия дополнительной спецификации const во втором случае, то инициализация без такой спецификации считается лучше, поэтому перегрузка будет разрешена в пользу устоявшей функции manip(vector &).
Для второго вызова существует только одна устоявшая функция manip(const vector &). Поскольку фактический аргумент является временной переменной, содержащей результат, возвращенный f(), то такой аргумент представляет собой r-значение, которое нельзя использовать для инициализации неконстантного формального параметра-ссылки функции manip(vector &). Поэтому наилучшей является единственная устоявшая manip(const vector &).
Разумеется, у функций может быть несколько фактических аргументов. Выбор наилучшей из устоявших должен производиться с учетом ранжирования последовательностей преобразований всех аргументов. Рассмотрим пример:

Функция ff(), принимающая два аргумента типа int, выбирается в качестве наилучшей из устоявших по следующим причинам:

  1. ее первый аргумент лучше. 0 дает точное соответствие с формальным параметром типа int, тогда как для установления соответствия с параметром типа char * требуется стандартное преобразование указателя;
  2. ее второй аргумент имеет тот же ранг. К аргументу ‘a’ типа char для установления соответствия со вторым формальным параметром любой из двух функций должна быть применена последовательность преобразований, имеющая ранг расширения типа.

Вот еще один пример:

Обе функции compute( const int&, short ) и compute( int&, double ) устояли. Вторая выбирается в качестве наилучшей по следующим причинам:

  1. ее первый аргумент лучше. Инициализация ссылки для первой устоявшей функции хуже потому, что она требует добавления спецификатора const, не нужного для второй функции;
  2. ее второй аргумент имеет тот же ранг. К аргументу ‘c’ типа char для установления соответствия со вторым формальным параметром любой из двух функций должна быть применена последовательность трансформаций, имеющая ранг стандартного преобразования.

9.4.4. Аргументы со значениями по умолчанию

Наличие аргументов со значениями по умолчанию способно расширить множество устоявших функций. Устоявшими являются функции, которые вызываются с данным списком фактических аргументов. Но такая функция может иметь больше формальных параметров, чем задано фактических аргументов, в том случае, когда для каждого неуказанного параметра есть некое значение по умолчанию:

Для первого и третьего вызовов функция ff() является устоявшей, хотя передан всего один фактический аргумент. Это обусловлено следующими причинами:

  1. для второго формального параметра есть значение по умолчанию;
  2. первый параметр типа long точно соответствует фактическому аргументу в первом вызове и может быть приведен к типу аргумента в третьем вызове за счет последовательности, имеющей ранг стандартного преобразования.

Последний вызов является неоднозначным, поскольку обе устоявших функции могут быть выбраны, если применить стандартное преобразование к первому аргументу. Функции ff(int) не отдается предпочтение только потому, что у нее один параметр.

Упражнение 9.9

Объясните, что происходит при разрешении перегрузки для вызова функции compute() внутри main(). Какие функции являются кандидатами? Какие из них устоят после первого шага? Какие последовательности преобразований надо применить к фактическому аргументу, чтобы он соответствовал формальному параметру для каждой устоявшей функции? Какая функция будет наилучшей из устоявших?

Что будет, если using-объявление поместить внутрь main() перед вызовом compute()? Ответьте на те же вопросы.

Функции (functions) в C++: перегрузки и прототипы

Привет, дорогой читатель! Вам, наверное, приходилось в программе использовать один и тот же блок кода несколько раз. Например, выводить на экран одну и ту же строчку. Для того, чтобы каждый раз не писать одинаковый блок кода в C++, присутствуют функции.

Сегодня мы разберем, что такое функции и как правильно их использовать в своей программе. Поехали!

Что такое функции

Функции — это блок кода, который вы можете использовать в любом участке вашей программы неограниченное количество раз. Например, в программе ниже мы выводим 2 строки (без применения функций):

А вот если бы мы использовали функции, то у нас получилось бы так:

Мы хотим, чтобы вы обратили внимание на увеличение количества строк в первой программе при выводе этих двух строк 5 раз.

Как видите, если правильно применять функции, то можно уменьшить программу в несколько раз. Но вы должны помнить — бессмысленно использовать функции без видимых оснований (например, если логика внутри функции слишком специфична).

Вашему компилятору будет совершенно без разницы, использовали вы функции или несколько раз вставили одинаковый блок кода, в итоге он выведет одинаковый результат.

Чтобы понять, как работают локальные переменные (например, переменные в функциях) и глобальные переменные, можете почитать данную статью.

Как создать функции в C++

Таким образом, чтобы создать функции, нужно использовать конструкцию, которая находится пониже:

Давайте разберем эту конструкцию:

  • Тип данных функции. В самом начале нам нужно указать тип данных, который в конечном итоге будет передавать функция.
  • Имя функции. Нам нужно задать функции имя (исключениями являются зарезервированные слова в C++, имена начинающиеся с цифр, а также имена разделенные пробелом).
  • Аргументы функции. В скобках (после имени функции) могут находиться аргументы функции. Аргумент функции — это значение, которое можно передать функции при ее вызове. Если аргумент функции не один, а их несколько, то их нужно разделять запятой.

Если аргументов в функции нет, то в скобках можно указать тип void . Но писать его необязательно, он стоит по умолчанию.

  • Блок кода. После открывающей скобки идет блок кода, который будет начинать работать при вызове функции.

Если вы не знали main() — это тоже функция.

Как вызывать функцию

Для вызова функций вам нужно использовать такую конструкцию:

Например, выше для вызова функции stroka() (эта функция находится выше) нам нужно использовать такую конструкцию:

Как видите, мы не вписывали аргументы в круглые скобки, так как мы их не указали при создании функции.

Зачем использовать функции

Практически все программисты на текущее время постоянно используют функции, поскольку это экономит их время и позволяет более интенсивно работать. Особенно хорошо отзываются те программисты, которые уже работали над большими проектами, имеющими тысячи строк кода за собой.

Дело в том, что, если вы не используете функции, то программа становится менее мобильной. Например, конкретная ситуация. Вы работаете над проектом и уже написали несколько тысяч строк кода, затем решили изменить вывод сообщения с «Привет!» на «Привет дорогой читатель!» . Для этого вам придется найти все строки, в которых происходит вывод сообщения и изменить их все.

А если бы вы использовали функцию, которая выводила сообщение «Привет!», то тогда бы вам пришлось только найти эту функцию и изменить ее!

Перегрузка функций

В С++ вы можете создавать функции с одинаковыми именами. Вы наверно удивлены, что такое вообще возможно так, как если у нас будет 3 функции с одинаковыми именами и мы захотим вызвать одну из этих функций, то мы таким образом вызовем все 3 функции и получится полная каша. Но компилятор думает про это совершенно по-другому.

Все дело в том, что у каждой функции есть свое полное имя (или по-другому сигнатура). Параметры функции — это вся информация о функции. В эту информацию входят:

  • Имя функции.
  • Число аргументов функции.
  • Типы аргументов.

Именно поэтому компилятор считает функции с одинаковыми именами разными, если сигнатуры соответственно тоже разные.

Перегрузка функций — это создание функций с одинаковыми именами, но с разными сигнатурами (полными именами).

В примере ниже все функции разные, хотя и имена у них одинаковые:

Задача на перегрузку функций в C++, которая может оказаться сложнее, чем выглядит

Большое спасибо за доступное изложение материала! ��


Код не работает — ошибка: error C2668: ‘sum’ : ambiguous call to overloaded function
насколько я понял, константы 5.0 и 6.0 которые передаются в функции, компилятор определяет как double, а поскольку функии принимающей в параметрах double нет, компилятор пытается преобразовать в другой подходящий тип — int или float, да вот не может выбрать в какой лучше. Если задать хоть один аргумент явно int или float, проблем не будет.

Автор сайта отвечает :
Всё так и есть. Можно еще дописывать дополнительную функцию для double или же просто использовать приведение прямо в момент передачи в функцию.
myfunc(5.f,6.f);

Или опечатка или ошибка — там, где впервые суммируются 100.3 и 220.3 получится 320.3 (220.3 приведется к int=220), а во втором 320.6

Перегрузка операторов в C++

Доброго времени суток!

Желание написать данную статью появилось после прочтения поста Перегрузка C++ операторов, потому что в нём не были раскрыты многие важные темы.

Самое главное, что необходимо помнить — перегрузка операторов, это всего лишь более удобный способ вызова функций, поэтому не стоит увлекаться перегрузкой операторов. Использовать её следует только тогда, когда это упростит написание кода. Но, не настолько, чтобы это затрудняло чтение. Ведь, как известно, код читается намного чаще, чем пишется. И не забывайте, что вам никогда не дадут перегрузить операторы в тандеме со встроенными типами, возможность перегрузки есть только для пользовательских типов/классов.

Синтаксис перегрузки

Синтаксис перегрузки операторов очень похож на определение функции с именем operator@, где @ — это идентификатор оператора (например +, -, >). Рассмотрим простейший пример:

В данном случае, оператор оформлен как член класса, аргумент определяет значение, находящееся в правой части оператора. Вообще, существует два основных способа перегрузки операторов: глобальные функции, дружественные для класса, или подставляемые функции самого класса. Какой способ, для какого оператора лучше, рассмотрим в конце топика.

В большинстве случаев, операторы (кроме условных) возвращают объект, или ссылку на тип, к которому относятся его аргументы (если типы разные, то вы сами решаете как интерпретировать результат вычисления оператора).

Перегрузка унарных операторов

Рассмотрим примеры перегрузки унарных операторов для определенного выше класса Integer. Заодно определим их в виде дружественных функций и рассмотрим операторы декремента и инкремента:

Теперь вы знаете, как компилятор различает префиксные и постфиксные версии декремента и инкремента. В случае, когда он видит выражение ++i, то вызывается функция operator++(a). Если же он видит i++, то вызывается operator++(a, int). То есть вызывается перегруженная функция operator++, и именно для этого используется фиктивный параметр int в постфиксной версии.

Бинарные операторы

Рассмотрим синтаксис перегрузки бинарных операторов. Перегрузим один оператор, который возвращает l-значение, один условный оператор и один оператор, создающий новое значение (определим их глобально):

Во всех этих примерах операторы перегружаются для одного типа, однако, это необязательно. Можно, к примеру, перегрузить сложение нашего типа Integer и определенного по его подобию Float.

Аргументы и возвращаемые значения

Как можно было заметить, в примерах используются различные способы передачи аргументов в функции и возвращения значений операторов.

  • Если аргумент не изменяется оператором, в случае, например унарного плюса, его нужно передавать как ссылку на константу. Вообще, это справедливо для почти всех арифметических операторов (сложение, вычитание, умножение. )
  • Тип возвращаемого значения зависит от сути оператора. Если оператор должен возвращать новое значение, то необходимо создавать новый объект (как в случае бинарного плюса). Если вы хотите запретить изменение объекта как l-value, то нужно возвращать его константным.
  • Для операторов присваивания необходимо возвращать ссылку на измененный элемент. Также, если вы хотите использовать оператор присваивания в конструкциях вида (x=y).f(), где функция f() вызывается для для переменной x, после присваивания ей y, то не возвращайте ссылку на константу, возвращайте просто ссылку.
  • Логические операторы должны возвращать в худшем случае int, а в лучшем bool.

Оптимизация возвращаемого значения

При создании новых объектов и возвращении их из функции следует использовать запись как для вышеописанного примера оператора бинарного плюса.

Честно говоря, не знаю, какая ситуация актуальна для C++11, все рассуждения далее справедливы для C++98.
На первый взгляд, это похоже на синтаксис создания временного объекта, то есть как будто бы нет разницы между кодом выше и этим:

Но на самом деле, в этом случае произойдет вызов конструктора в первой строке, далее вызов конструктора копирования, который скопирует объект, а далее, при раскрутке стека вызовется деструктор. При использовании первой записи компилятор изначально создаёт объект в памяти, в которую нужно его скопировать, таким образом экономится вызов конструктора копирования и деструктора.

Особые операторы

В C++ есть операторы, обладающие специфическим синтаксисом и способом перегрузки. Например оператор индексирования []. Он всегда определяется как член класса и, так как подразумевается поведение индексируемого объекта как массива, то ему следует возвращать ссылку.

Оператор запятая

В число «особых» операторов входит также оператор запятая. Он вызывается для объектов, рядом с которыми поставлена запятая (но он не вызывается в списках аргументов функций). Придумать осмысленный пример использования этого оператора не так-то просто. Хабраюзер AxisPod в комментариях к предыдущей статье о перегрузке рассказал об одном.

Оператор разыменования указателя

Перегрузка этих операторов может быть оправдана для классов умных указателей. Этот оператор обязательно определяется как функция класса, причём на него накладываются некоторые ограничения: он должен возвращать либо объект (или ссылку), либо указатель, позволяющий обратиться к объекту.


Оператор присваивания

Оператор присваивания обязательно определяется в виде функции класса, потому что он неразрывно связан с объектом, находящимся слева от «=». Определение оператора присваивания в глобальном виде сделало бы возможным переопределение стандартного поведения оператора «=». Пример:

Как можно заметить, в начале функции производится проверка на самоприсваивание. Вообще, в данном случае самоприсваивание безвредно, но ситуация не всегда такая простая. Например, если объект большой, можно потратить много времени на ненужное копирование, или при работе с указателями.

Неперегружаемые операторы

Некоторые операторы в C++ не перегружаются в принципе. По всей видимости, это сделано из соображений безопасности.

  • Оператор выбора члена класса «.».
  • Оператор разыменования указателя на член класса «.*»
  • В С++ отсутствует оператор возведения в степень (как в Fortran) «**».
  • Запрещено определять свои операторы (возможны проблемы с определением приоритетов).
  • Нельзя изменять приоритеты операторов

Перегрузка функций Function Overloading

C++ позволяет определять несколько функций с одинаковым именем в одной области. C++ allows specification of more than one function of the same name in the same scope. При вызове этих функций перегружены функции. These functions are called overloaded functions. Перегруженные функции позволяют указать различную семантику для функции, в зависимости от типов и числа аргументов. Overloaded functions enable you to supply different semantics for a function, depending on the types and number of arguments.

Например print функцию, которая принимает std::string аргумент может выполнять совершенно разные задачи, чем та, которая принимает аргумент типа двойные. For example, a print function that takes a std::string argument might perform very different tasks than one that takes an argument of type double. Перегрузка избавляет от необходимости использовать имена, такие как print_string или print_double . Overloading saves you from having to use names such as print_string or print_double . Во время компиляции компилятор выбирает перегрузку, которую нужно использовать в зависимости от типа аргументов, передаваемых в вызывающим объектом. At compile time, the compiler chooses which overload to use based on the type of arguments passed in by the caller. При вызове метода print(42.0) , а затем void print(double d) функция будет вызываться. If you call print(42.0) , then the void print(double d) function will be invoked. При вызове метода print(«hello world») , а затем void print(std::string) перегрузки будет вызываться. If you call print(«hello world») , then the void print(std::string) overload will be invoked.

Можно перегрузить функции-члены и функции, не являющихся членами. You can overload both member functions and non-member functions. В следующей таблице указаны компоненты объявления функций, используемые языком C++ для различения групп функций с одинаковым именем в одной области. The following table shows what parts of a function declaration C++ uses to differentiate between groups of functions with the same name in the same scope.

Заметки по перегрузке Overloading Considerations

Элемент объявления функции Function Declaration Element Использование для перегрузки Used for Overloading?
Тип возвращаемого функцией значения Function return type Нет No
Число аргументов Number of arguments Да Yes
Тип аргументов Type of arguments Да Yes
Наличие или отсутствие многоточия Presence or absence of ellipsis Да Yes
Использование typedef имена Use of typedef names Нет No
Незаданные границы массива Unspecified array bounds Нет No
const или volatile const or volatile Да, при применении к всей функции Yes, when applied to entire function
Ref квалификаторы Ref-qualifiers Да Yes

Пример Example

В следующем примере показано использование перегрузки. The following example illustrates how overloading can be used.

В приведенном выше коде отображается перегрузка функции print в области видимости файла. The preceding code shows overloading of the print function in file scope.

Аргумент по умолчанию не считается частью типа функции. The default argument isn’t considered part of the function type. Таким образом он не используется при выборке перегруженных функций. Therefore, it’s not used in selecting overloaded functions. Две функции, которые различаются только в своих аргументах, считаются множественными определениями, а не перегруженными функциями. Two functions that differ only in their default arguments are considered multiple definitions rather than overloaded functions.

Аргументы по умолчанию не могут задаваться для перегруженных операторов. Default arguments can’t be supplied for overloaded operators.

Сопоставление аргументов Argument Matching

Перегруженные функции выбираются для оптимального соответствия объявлений функций в текущей области аргументам, предоставленным в вызове функции. Overloaded functions are selected for the best match of function declarations in the current scope to the arguments supplied in the function call. Если подходящая функция найдена, эта функция вызывается. If a suitable function is found, that function is called. «Подходящая» в данном контексте означает либо. «Suitable» in this context means either:

Точное соответствие найдено. An exact match was found.

Тривиальное преобразование выполнено. A trivial conversion was performed.

Восходящее приведение целого типа выполнено. An integral promotion was performed.

Стандартное преобразование в требуемый тип аргумента существует. A standard conversion to the desired argument type exists.

Пользовательское преобразование (оператор преобразования или конструктор) в требуемый тип аргумента существует. A user-defined conversion (either conversion operator or constructor) to the desired argument type exists.

Аргументы, представленные многоточием, найдены. Arguments represented by an ellipsis were found.

Компилятор создает набор функций-кандидатов для каждого аргумента. The compiler creates a set of candidate functions for each argument. Функции-кандидаты — это функции, в которых фактический аргумент в данной позиции можно преобразовать в тип формального аргумента. Candidate functions are functions in which the actual argument in that position can be converted to the type of the formal argument.

Для каждого аргумента создается набор наиболее подходящих функций, и выбранная функция представляет собой пересечение всех наборов. A set of «best matching functions» is built for each argument, and the selected function is the intersection of all the sets. Если на пересечении находится несколько функций, перегрузка является неоднозначной и выдает ошибку. If the intersection contains more than one function, the overloading is ambiguous and generates an error. Функция, которая выбирается в конечном итоге, всегда является самой подходящей по сравнению с остальными функциями в группе по крайней мере для одного аргумента. The function that is eventually selected is always a better match than every other function in the group for at least one argument. Если не выявляет победителя, вызов функции приводит к ошибке. If there’s no clear winner, the function call generates an error.

Рассмотрим следующие объявления (функции отмечены как Variant 1 , Variant 2 и Variant 3 для ссылки в последующем обсуждении). Consider the following declarations (the functions are marked Variant 1 , Variant 2 , and Variant 3 , for identification in the following discussion):

Рассмотрим следующий оператор. Consider the following statement:

Представленный выше оператор создает два набора. The preceding statement builds two sets:

Набор 1: Функции-кандидаты, имеющие первый аргумент дробного типа Set 1: Candidate Functions That Have First Argument of Type Fraction Набор 2: Кандидат функции которых второй аргумент можно преобразовать к типу int Set 2: Candidate Functions Whose Second Argument Can Be Converted to Type int
Variant 1 Variant 1 Вариант 1 (int может быть преобразован в long с помощью стандартного преобразования) Variant 1 (int can be converted to long using a standard conversion)
Variant 3 Variant 3

Функции в наборе 2 являются функции существует, для которых являются неявные преобразования из типа фактического параметра в тип формального параметра, и среди таких функций имеется функция, для которой является «стоимость» преобразование типа фактического параметра в тип формального параметра наименьшее. Functions in Set 2 are functions for which there are implicit conversions from actual parameter type to formal parameter type, and among such functions there’s a function for which the «cost» of converting the actual parameter type to its formal parameter type is the smallest.

Пересечением этих двух наборов является функция Variant 1. The intersection of these two sets is Variant 1. Ниже представлен пример неоднозначного вызова функции. An example of an ambiguous function call is:

В предыдущем вызове функции создаются следующие наборы. The preceding function call builds the following sets:

Набор 1: Кандидат функции, имеющие первый аргумент типа int Set 1: Candidate Functions That Have First Argument of Type int Набор 2: Кандидат функции, имеющие второй аргумент типа int Set 2: Candidate Functions That Have Second Argument of Type int
Variant 2 (int может быть преобразован в long с помощью стандартного преобразования) Variant 2 (int can be converted to long using a standard conversion) Вариант 1 (int может быть преобразован в long с помощью стандартного преобразования) Variant 1 (int can be converted to long using a standard conversion)

Поскольку пересечение этих двух наборов является пустым, компилятор выдает сообщение об ошибке. Because the intersection of these two sets is empty, the compiler generates an error message.

Для сопоставления функции с помощью аргументов n аргументы по умолчанию рассматривается как n+ 1 отдельные функции, каждая из которых Разное количество аргументов. For argument matching, a function with n default arguments is treated as n+1 separate functions, each with a different number of arguments.

Многоточие (. ) выступает в качестве подстановочного знака; оно соответствует любому фактическому аргументу. The ellipsis (. ) acts as a wildcard; it matches any actual argument. Он может привести к множества неоднозначных наборов, если вы не разрабатываете наборов перегруженных функций с особой осторожностью. It can lead to many ambiguous sets, if you don’t design your overloaded function sets with extreme care.

Неоднозначность перегруженных функций невозможно определить, пока не будет обнаружен вызов функции. Ambiguity of overloaded functions can’t be determined until a function call is encountered. На этом этапе наборы создаются для каждого аргумента в вызове функции, и можно определить, существует ли неоднозначная перегрузка. At that point, the sets are built for each argument in the function call, and you can determine whether an unambiguous overload exists. Это означает, что неоднозначности могут оставаться в коде до тех пор, пока они не будут вызваны конкретным вызовом функции. This means that ambiguities can remain in your code until they are evoked by a particular function call.

Различия типов аргументов Argument Type Differences

Перегруженные функции различают типы аргументов, имеющие разные инициализаторы. Overloaded functions differentiate between argument types that take different initializers. Следовательно, аргумент заданного типа и ссылка на этот тип считаются одинаковыми для перегрузки, Therefore, an argument of a given type and a reference to that type are considered the same for the purposes of overloading. поскольку имеют одни и те же инициализаторы. They are considered the same because they take the same initializers. Например, max( double, double ) — то же самое, что и max( double &, double & ) . For example, max( double, double ) is considered the same as max( double &, double & ) . Объявление двух таких функций приводит к ошибке. Declaring two such functions causes an error.

По этой же причине аргументы функций, тип которых изменен с const или volatile не обрабатываются иначе, чем базовый тип для перегрузки. For the same reason, function arguments of a type modified by const or volatile are not treated differently than the base type for the purposes of overloading.

Однако механизм перегрузки функций может различать ссылки, которые определяются const и volatile и ссылки на базовый тип. However, the function overloading mechanism can distinguish between references that are qualified by const and volatile and references to the base type. Это повышает кода приведенному ниже: It makes code such as the following possible:

Вывод Output

Указатели на const и volatile объекты также считаются отличными от указателей на базовый тип для перегрузки. Pointers to const and volatile objects are also considered different from pointers to the base type for the purposes of overloading.

Сопоставление аргументов и преобразования Argument matching and conversions

Когда компилятор пытается сопоставить фактические аргументы с аргументами в объявлениях функций и точное соответствие найти не удается, для получения правильного типа он может выполнять стандартные или пользовательские преобразования. When the compiler tries to match actual arguments against the arguments in function declarations, it can supply standard or user-defined conversions to obtain the correct type if no exact match can be found. Для преобразований действуют следующие правила: The application of conversions is subject to these rules:

последовательности преобразований, содержащие несколько пользовательских преобразований, не учитываются; Sequences of conversions that contain more than one user-defined conversion are not considered.

последовательности преобразований, которые могут быть сокращены путем удаления промежуточных преобразований, не учитываются. Sequences of conversions that can be shortened by removing intermediate conversions are not considered.

Получающаяся последовательность преобразований (если таковые имеются), называется наилучшей последовательностью сопоставления. The resultant sequence of conversions, if any, is called the best matching sequence. Существует несколько способов преобразования объекта типа int ввода unsigned long с использованием стандартных преобразований (описанных в стандартные преобразования): There are several ways to convert an object of type int to type unsigned long using standard conversions (described in Standard Conversions):

Преобразование из int для long и затем из long для unsigned long. Convert from int to long and then from long to unsigned long.


Преобразование из int для unsigned long. Convert from int to unsigned long.

Первая последовательность, хотя и обеспечивает достижение требуемой цели, не наилучшей последовательностью сопоставления, существует более короткая последовательность. The first sequence, although it achieves the desired goal, isn’t the best matching sequence — a shorter sequence exists.

В представленной ниже таблице показана группа преобразований, называемых тривиальными. Они оказывают ограниченное влияние на определение наилучшей последовательности сопоставления. The following table shows a group of conversions, called trivial conversions, that have a limited effect on determining which sequence is the best matching. В списке, приведенном после таблицы, рассматриваются экземпляры, в которых тривиальные преобразования влияют на выбор последовательности. The instances in which trivial conversions affect choice of sequence are discussed in the list following the table.

Тривиальные преобразования Trivial Conversions

Тип, из которого выполняется преобразование Convert from Type Тип, в который выполняется преобразование Convert to Type
Имя типа type-name Имя типа & type-name &
Имя типа & type-name & Имя типа type-name
Имя типа ] type-name [ ] Имя типа * type-name *
Имя типа ( список аргументов ) type-name ( argument-list ) ( * имя типа ) ( список аргументов ) ( * type-name ) ( argument-list )
Имя типа type-name const имя типа const type-name
Имя типа type-name volatile имя типа volatile type-name
Имя типа * type-name * const имя типа * const type-name *
Имя типа * type-name * volatile имя типа * volatile type-name *

Ниже приведена последовательность, в которой делаются попытки выполнения преобразований. The sequence in which conversions are attempted is as follows:

Точное соответствие. Exact match. Точное соответствие между типами, с которыми функция вызывается, и типами, объявленными в прототипе функции, всегда является наилучшим соответствием. An exact match between the types with which the function is called and the types declared in the function prototype is always the best match. Последовательности тривиальных преобразований классифицируются как точные соответствия. Sequences of trivial conversions are classified as exact matches. Тем не менее последовательностей, не делайте никакие из этих преобразований, считаются лучше, чем последовательности, которые преобразуют: However, sequences that don’t make any of these conversions are considered better than sequences that convert:

Из указателя в указатель на const ( type * для const type * ). From pointer, to pointer to const ( type * to const type *).

Из указателя в указатель на volatile ( type * для volatile type *). From pointer, to pointer to volatile ( type * to volatile type *).

Из указателя в указатель на const ( type & для const type &). From reference, to reference to const ( type & to const type &).

Из указателя в указатель на volatile ( type & для volatile type &). From reference, to reference to volatile ( type & to volatile type &).

Сопоставление с использованием повышений. Match using promotions. Любая последовательность, не классифицированная как точное соответствие, содержащий только восходящие приведения целого типа, преобразования из float для двойные, и тривиальные преобразования, классифицируется как сопоставление с использованием повышений. Any sequence not classified as an exact match that contains only integral promotions, conversions from float to double, and trivial conversions is classified as a match using promotions. Хотя сопоставление с использованием повышений не такое хорошее, как точное, оно лучше сопоставления с использованием стандартных преобразований. Although not as good a match as any exact match, a match using promotions is better than a match using standard conversions.

Сопоставление с использованием стандартных преобразований. Match using standard conversions. Любая последовательность, не классифицированная как точное соответствие или сопоставление с использованием повышений и содержащая только стандартные и тривиальные преобразования, классифицируется как сопоставление с использованием стандартных преобразований. Any sequence not classified as an exact match or a match using promotions that contains only standard conversions and trivial conversions is classified as a match using standard conversions. В этой категории применяются следующие правила: Within this category, the following rules are applied:

Преобразование из указателя на производный класс в указатель на прямой или косвенный базовый класс предпочтительнее преобразования в void * или const void * . Conversion from a pointer to a derived class, to a pointer to a direct or indirect base class is preferable to converting to void * or const void * .

преобразование из указателя на производный класс в указатель на базовый класс создает тем более хорошее соответствие, чем ближе базовый класс к прямому базовому классу. Conversion from a pointer to a derived class, to a pointer to a base class produces a better match the closer the base class is to a direct base class. Предположим, что иерархия классов имеет вид, показанный на следующем рисунке. Suppose the class hierarchy is as shown in the following figure.

Диаграмма, показывающая предпочтительные преобразования Graph showing preferred conversions

Преобразование из типа D* в тип C* предпочтительнее преобразования из типа D* в тип B* . Conversion from type D* to type C* is preferable to conversion from type D* to type B* . Аналогично, преобразование из типа D* в тип B* предпочтительнее преобразования из типа D* в тип A* . Similarly, conversion from type D* to type B* is preferable to conversion from type D* to type A* .

Это же правило применяется для преобразований ссылок. This same rule applies to reference conversions. Преобразование из типа D& в тип C& предпочтительнее преобразования из типа D& в тип B& и т. д. Conversion from type D& to type C& is preferable to conversion from type D& to type B& , and so on.

Это же правило применяется для преобразований указателей на член. This same rule applies to pointer-to-member conversions. Преобразование из типа T D::* в тип T C::* предпочтительнее преобразования из типа T D::* в тип T B::* и т. д. ( T — тип члена.) Conversion from type T D::* to type T C::* is preferable to conversion from type T D::* to type T B::* , and so on (where T is the type of the member).

Предыдущее правило применяется только в определенном пути наследования. The preceding rule applies only along a given path of derivation. Рассмотрим граф, показанный на следующем рисунке. Consider the graph shown in the following figure.

Граф множественного наследования, показывающее предпочтительные преобразования Multiple-inheritance graph that shows preferred conversions

Преобразование из типа C* в тип B* предпочтительнее преобразования из типа C* в тип A* . Conversion from type C* to type B* is preferable to conversion from type C* to type A* . Причина заключается в том, что эти преобразования находятся на одном пути и узел B* ближе. The reason is that they are on the same path, and B* is closer. Однако преобразование из типа C* ввода D* не предпочтительнее преобразования в тип A* ; нет предпочтений нет, поскольку преобразования осуществляются с использованием разных путей. However, conversion from type C* to type D* isn’t preferable to conversion to type A* ; there’s no preference because the conversions follow different paths.

Сопоставление с пользовательскими преобразованиями. Match with user-defined conversions. Эта последовательность невозможно классифицировать как точное соответствие, сопоставление с использованием повышений или сопоставление с использованием стандартных преобразований. This sequence can’t be classified as an exact match, a match using promotions, or a match using standard conversions. Чтобы последовательность можно было классифицировать как сопоставление с пользовательскими преобразованиями, она должна содержать только пользовательские, стандартные или тривиальные преобразования. The sequence must contain only user-defined conversions, standard conversions, or trivial conversions to be classified as a match with user-defined conversions. Сопоставление с пользовательскими преобразованиями лучше сопоставления с многоточием, но хуже сопоставления со стандартными преобразованиями. A match with user-defined conversions is considered a better match than a match with an ellipsis but not as good a match as a match with standard conversions.

Сопоставление с многоточием. Match with an ellipsis. Любая последовательность, соответствующая многоточию в объявлении, классифицируется как сопоставление с многоточием. Any sequence that matches an ellipsis in the declaration is classified as a match with an ellipsis. Он самое слабое соответствие. It’s considered the weakest match.

Пользовательские преобразования применяются при отсутствии встроенного повышения или преобразования. User-defined conversions are applied if no built-in promotion or conversion exists. Эти преобразования выбираются на основе типа сопоставляемого аргумента. These conversions are selected on the basis of the type of the argument being matched. Рассмотрим следующий код. Consider the following code:

Доступные заданные пользователем преобразования для класса UDC : из типа int и тип long. The available user-defined conversions for class UDC are from type int and type long. Поэтому компилятор проверяет преобразования для типа сопоставляемого объекта: UDC . Therefore, the compiler considers conversions for the type of the object being matched: UDC . Преобразование в int существует, и он выбран. A conversion to int exists, and it is selected.

В процессе сопоставления аргументов стандартные преобразования можно применять как к аргументу, так и к результату пользовательского преобразования. During the process of matching arguments, standard conversions can be applied to both the argument and the result of a user-defined conversion. Поэтому следующий код работает. Therefore, the following code works:

В предыдущем примере, определенное пользователем преобразование long-оператор, вызывается для преобразования udc ввода long. In the preceding example, the user-defined conversion, operator long, is invoked to convert udc to type long. Если нет определенное пользователем преобразование в тип long был определен, преобразование продолжилось бы следующим образом: Тип UDC был бы преобразован в тип int с использованием определенного пользователем преобразования. If no user-defined conversion to type long had been defined, the conversion would have proceeded as follows: Type UDC would have been converted to type int using the user-defined conversion. Затем стандартное преобразование из типа int ввода long была реализована в соответствии с аргументом в объявлении. Then the standard conversion from type int to type long would have been applied to match the argument in the declaration.

Если для сопоставления аргумента необходимы все заданные пользователем преобразования, стандартные преобразования не используются при оценке наилучшего соответствия. If any user-defined conversions are required to match an argument, the standard conversions aren’t used when evaluating the best match. Даже если более одного кандидата функции требуется определенное пользователем преобразование, функции считаются одинаковыми. Even if more than one candidate function requires a user-defined conversion, the functions are considered equal. Пример: For example:

Обе версии Func требуют определенного пользователем преобразования для преобразования типа int аргументу типа класса. Both versions of Func require a user-defined conversion to convert type int to the class type argument. Возможные преобразования: The possible conversions are:

Преобразование из типа int ввода UDC1 (пользовательское преобразование). Convert from type int to type UDC1 (a user-defined conversion).

Преобразование из типа int ввода long; затем преобразование в тип UDC2 (двухступенчатое преобразование). Convert from type int to type long; then convert to type UDC2 (a two-step conversion).

Несмотря на то, что второй требуется стандартное преобразование и пользовательское преобразование, два преобразования все равно считаются одинаковыми. Even though the second one requires both a standard conversion and the user-defined conversion, the two conversions are still considered equal.

Пользовательские преобразования считаются преобразованиями посредством создания или инициализации (функции преобразования). User-defined conversions are considered conversion by construction or conversion by initialization (conversion function). При рассмотрении наилучшего соответствия оба метода считаются одинаковыми. Both methods are considered equal when considering the best match.

Сопоставление аргументов и указатель this Argument matching and the this pointer

Функции-члены класса обрабатываются по-разному, в зависимости от того, они будут объявлены как статических. Class member functions are treated differently, depending on whether they are declared as static. Поскольку нестатические функции имеют неявный аргумент, который предоставляет это указатель, считается, что нестатические функции имеют один аргумент больше, чем статические функции; в противном случае они объявляются одинаково. Because nonstatic functions have an implicit argument that supplies the this pointer, nonstatic functions are considered to have one more argument than static functions; otherwise, they are declared identically.

Эти нестатические функции-члены требуют неявный это указатель соответствовал типу объекта, через который вызывается функция, или, для перегруженных операторов, они требуют, что первый аргумент совпадал с объект, для которого применяется оператор. These nonstatic member functions require that the implied this pointer match the object type through which the function is being called, or, for overloaded operators, they require that the first argument match the object on which the operator is being applied. (Дополнительные сведения о перегруженных операторах см. в разделе перегруженные операторы.) (For more information about overloaded operators, see Overloaded Operators.)

В отличие от других аргументов в перегруженных функций, представленные никакие временные объекты и преобразования не применяются при попытке сопоставить это аргументом указателя. Unlike other arguments in overloaded functions, no temporary objects are introduced and no conversions are attempted when trying to match the this pointer argument.

Когда -> оператора выбора члена используется для доступа к функции-члена класса class_name , это аргумент-указатель с типом class_name * const . When the -> member-selection operator is used to access a member function of class class_name , the this pointer argument has a type of class_name * const . Если элементы объявлены как const или volatile, типы, const class_name * const и volatile class_name * const , соответственно. If the members are declared as const or volatile, the types are const class_name * const and volatile class_name * const , respectively.

Оператор выбора члена . работает точно так же, за исключением того, что в качестве префикса к имени объекта подставляется неявный оператор взятия адреса & . The . member-selection operator works exactly the same way, except that an implicit & (address-of) operator is prefixed to the object name. В следующем примере показано, как это делается: The following example shows how this works:

С точки зрения сопоставления аргументов, левый операнд операторов ->* и .* (указатель на член) обрабатывается так же, как и для операторов . и -> (выбор члена). The left operand of the ->* and .* (pointer to member) operators are treated the same way as the . and -> (member-selection) operators with respect to argument matching.

Ref квалификаторы для функций-членов Ref-qualifiers on member functions

Квалификаторы ref делают возможным перегрузить функцию-член на основании ли объект указывает это rvalue или lvalue. Ref qualifiers make it possible to overload a member function on the basis of whether the object pointed to by this is an rvalue or an lvalue. Эту функцию можно использовать чтобы избежать ненужных операций копирования в сценариях, где пользователь не указывает указатель доступ к данным. This feature can be used to avoid unnecessary copy operations in scenarios where you choose not to provide pointer access to the data. Предположим, например, класс C некоторые данные в его конструктор инициализирует и возвращает копию этих данных в функции-члене get_data() . For example, assume class C initializes some data in its constructor, and returns a copy of that data in member function get_data() . Если тип объекта C представляет собой rvalue, уничтожить, то компилятор выберет get_data() && перегрузку, которая перемещает данные, чем скопировать его. If an object of type C is an rvalue that is about to be destroyed, then the compiler will choose the get_data() && overload, which moves the data rather than copy it.

Ограничения по перегрузке Restrictions on overloading

К допустимому набору перегруженных функций применяется несколько ограничений. Several restrictions govern an acceptable set of overloaded functions:

Любые две функции в наборе перегруженных функций должны иметь разные списки аргументов. Any two functions in a set of overloaded functions must have different argument lists.

Перегрузка функций со списками аргументов одного типа лишь на основании возвращаемого типа недопустима. Overloading functions with argument lists of the same types, based on return type alone, is an error.

Блок, относящийся только к системам Microsoft Microsoft Specific

Можно перегрузить оператор new исключительно на основании возвращаемого типа, в частности, на основании указанного модификатора модели памяти. You can overload operator new solely on the basis of return type — specifically, on the basis of the memory-model modifier specified.

Завершение блока, относящегося только к системам Майкрософт END Microsoft Specific

Функции-члены Невозможно перегружать исключительно на основе один является статической, а вторая — нестатической. Member functions can’t be overloaded solely on the basis of one being static and the other nonstatic.

TypeDef объявления не определяют новые типы; они представляют синонимы для существующих типов. typedef declarations do not define new types; they introduce synonyms for existing types. Они не влияют на механизм перегрузки. They don’t affect the overloading mechanism. Рассмотрим следующий код. Consider the following code:

Две указанные выше функции имеют идентичные списки аргументов. The preceding two functions have identical argument lists. PSTR является синонимом для типа char * . PSTR is a synonym for type char * . В области члена этот код возвращает ошибку. In member scope, this code generates an error.


Перечисляемые типы являются отдельными типами и могут использоваться для различения перегруженных функций. Enumerated types are distinct types and can be used to distinguish between overloaded functions.

Типы «массив» и «указатель» считаются идентичными в целях различения между перегруженных функций, но только для однонаправленного распределяться массивов. The types «array of » and «pointer to» are considered identical for the purposes of distinguishing between overloaded functions, but only for singly dimensioned arrays. Вот почему эти перегруженные функции конфликтуют и появится сообщение об ошибке: That’s why these overloaded functions conflict and generate an error message:

В случае многомерных массивов второе и все последующие измерения являются частью типа. For multiply dimensioned arrays, the second and all succeeding dimensions are considered part of the type. Поэтому они используются для различения перегруженных функций. Therefore, they are used in distinguishing between overloaded functions:

Перегрузка, переопределение и скрытие Overloading, overriding, and hiding

Любые два объявления функции с одинаковым именем в одной области видимости могут ссылаться на одну функцию или на две разные перегруженные функции. Any two function declarations of the same name in the same scope can refer to the same function, or to two discrete functions that are overloaded. Если списки аргументов в объявлениях содержат аргументы эквивалентных типов (как описано в предыдущем разделе), эти объявления относятся к одной и той же функции. If the argument lists of the declarations contain arguments of equivalent types (as described in the previous section), the function declarations refer to the same function. В противном случае они ссылаются на две различные функции, которые выбираются с использованием перегрузки. Otherwise, they refer to two different functions that are selected using overloading.

Область видимости класса строго соблюдается; Таким образом, функция, объявленная в базовом классе не находится в той же области, как функция объявлен в производном классе. Class scope is strictly observed; therefore, a function declared in a base class isn’t in the same scope as a function declared in a derived class. Если функция в производном классе объявлена с тем же именем, что и виртуальная функция в базовом классе, функция производного класса переопределяет функцию базового класса. If a function in a derived class is declared with the same name as a virtual function in the base class, the derived-class function overrides the base-class function. Дополнительные сведения см. в разделе виртуальные функции. For more information, see Virtual Functions.

Если функция базового класса не объявлена как «virtual», то функция производного класса говорят, что скрыть его. If the base class function isn’t declared as ‘virtual’, then the derived class function is said to hide it. Переопределение и скрытие отличаются от перегрузки. Both overriding and hiding are distinct from overloading.

Область видимости блока строго соблюдается; Таким образом, функция, объявленная в области видимости файла не находится в той же области, как функция объявлен локально. Block scope is strictly observed; therefore, a function declared in file scope isn’t in the same scope as a function declared locally. Если локально объявленная функция имеет то же имя, что и функция, объявленная в области файла, локально объявленная функция скрывает функцию области файла, не вызывая перегрузки. If a locally declared function has the same name as a function declared in file scope, the locally declared function hides the file-scoped function instead of causing overloading. Пример: For example:

В предыдущем коде показаны два определения функции func . The preceding code shows two definitions from the function func . Определение, которое принимает аргумент типа char * является локальным для main из-за extern инструкции. The definition that takes an argument of type char * is local to main because of the extern statement. Таким образом, определение, которое принимает аргумент типа int скрыта и первый вызов func произошла ошибка. Therefore, the definition that takes an argument of type int is hidden, and the first call to func is in error.

В случае перегруженных функций-членов различным версиям функции могут предоставляться разные права доступа. For overloaded member functions, different versions of the function can be given different access privileges. Они по-прежнему считаются находящимися в области видимости включающего класса и, таким образом, являются перегруженными функциями. They are still considered to be in the scope of the enclosing class and thus are overloaded functions. Рассмотрим следующий код, в котором функция-член Deposit перегружена; одна версия является открытой, вторая — закрытой. Consider the following code, in which the member function Deposit is overloaded; one version is public, the other, private.

Целью кода в примере является предоставление класса Account , в котором для внесения средств требуется правильный пароль. The intent of this sample is to provide an Account class in which a correct password is required to perform deposits. Это делается с помощью перегрузки. It’s done by using overloading.

Вызов Deposit в Account::Deposit вызывает закрытая функция-член. The call to Deposit in Account::Deposit calls the private member function. Это правильный вызов поскольку Account::Deposit является функцией-членом, и имеет доступ к закрытым членам класса. This call is correct because Account::Deposit is a member function, and has access to the private members of the class.

_Generic макрос

Дженерики

В Си стандарта С11 появились дженерик макросы, которые позволяют создавать функции общего назначения. Дженерик макрос позволяет выбрать функцию в зависимости от типа аргументов, переданных в функцию. Это, в некотором роде, сродни перегрузке методов по типу аргумента во многих языках, однако, это не динамическая диспетчеризация – определение типа аргумента происходит только на этапе компиляции.

Так как это нововведение Си 11, то для работы потребуется новая версия компилятора, которая поддерживает стандарт. Для примера используется gcc версии 5.3.0 с флагом -std=c11. На Windows без проблем ставится пакет MinGW, в состав которого входят все необходимые утилиты.

Рассмотрим на простом примере: функция возвращает переданный аргумент плюс один. Сначала определим все функции, которые могут быть выполнены, со всеми типами аргументов, которые нам необходимы

Далее сам макрос

Эта система похожа на оператор switch. Вместо foo будет подставлена одна из функций, а далее (X) – вызов с этим аргументом. Дефолтное значение – когда тип определить нельзя. Если тип не определён и нет дефолтного значения, или оно не может быть использовано функцией по умолчанию, то это ошибка. Вот программа

Для функции с двумя аргументами немного сложнее, надо описать все комбинации. В нашем случае функция может принимать аргументы типа float и int, складывать их и возвращать целое

Конечно, можно вместо трёх макросов объединить всё в один

Вот другой пример: макрос, который выводит имя типа, переданного в него

Заметьте важные особенности – без явного указания ‘a’ будет рассматриваться как int, а строка как модифицируемая, хотя попытка её изменить приведёт к ошибке. Для точного определениятипа можно его явно привести

DMIVK

Блог о современных технологиях

Перегрузка функций и процедур в C++

Всем привет! Свершилось чудо! Наконец-то руки дошли до написания статьи в раздел «Программирование». Сегодня я решил немного рассказать о перегрузке функций. Идея пришла как раз во время использования этой самой перегрузки в одной из своих программ.

Каждый программист знает, что любые имена в исходном коде программы следует задавать осмысленно. Конечно, компилятору абсолютно без разницы, какие названия вы используете. Хоть лбом в клавиатуру ударьте, набрав случайный набор букв и символов.
Но в дальнейшем читать код будет живой человек, так что использование хоть немного осмысленных имён обязательно. Именно для внесения ясности перегрузка функций и используется.

Перегрузка функций — своеобразная «фишка» языка С++, которой не было в С. В отличии от своего предшественника, С++, обращая внимание на количество значений, передаваемых в качестве аргументов, позволяет записать несколько функций с одинаковыми именами. Перегруженными называются функции и процедуры, имеющие одно и то же имя, но принимающие различные параметры. Передаваемые параметры могут отличаться как количеством, так и типом. Компилятор автоматически определит, какой из вариантов нужно вызвать в конкретной части программы.

Для чего нужна перегрузка функций?

Использование перегрузки чаще всего необходимо, когда несколько функций, выполняющих идентичные действия, должны принимать разные аргументы. Можно, конечно, каждый раз объявлять функцию с новым именем, но это усложнит понимание кода другими людьми. Да и сам программист способен запутаться в своём же коде. Таким образом, используя перегрузку, вы устраняете неясности и делаете код программы понятнее.

Рассмотрим пример. Не буду мудрить и напишу простой код для вычисления произведения двух чисел. Перегрузка функций выглядит следующим образом:

перегрузка функций против шаблонов функций

В книгах по C ++, почему нас учат использовать перегрузка функции когда у нас есть шаблоны в нашем распоряжении в C ++?

Не лучше ли показать эффективное (и правильное) использование шаблонов? В отличие от того, где перегрузка функций преподается в большинстве книг на C ++?

Или есть веские причины использовать один вместо другого?

Решение

Шаблоны дают преимущество, когда вы хотите выполнить одно и то же действие для типов, которые могут отличаться. Простой пример:

Вы можете использовать перегрузку, когда хотите применить различные операции в зависимости от типа:

Вы могли бы достичь вышеизложенного с помощью шаблонов и шаблонных специализаций, но такие специализации должны представлять несколько исключений из общего случая.

Другие решения

Шаблоны не могут принимать разное количество аргументов. Перегрузки могут. Кроме того, шаблон указывает, что вы можете работать с любым типом данных, но представлять это бессмысленно, хотя на самом деле подавляющее большинство шаблонов будут только специализациями (в вашей системе). Кроме того, перегрузки могут быть virtual а шаблонные специализации не могут. Также специализации не могут отличаться своими подписями от базы.

Это сильно ограничит возможные виды перегрузки по сравнению с существующей системой.

Обычно вы используете шаблоны, когда хотите выполнить один и тот же набор операций для разных типов данных.

Обычно вы используете перегрузку функций, когда хотите выполнять различные операции с определенными наборами данных.

Преимущество шаблонов в ситуации, когда вы хотите выполнить один и тот же набор операций для множества различных типов данных, заключается в том, что компилятор будет обрабатывать для вас во время компиляции любой возможный новый тип, который вы можете создать в будущем, который использует шаблонную функцию. если бы вы использовали перегрузку функций, вам пришлось бы создавать новую перегрузку функции каждый раз, когда вы создавали новый тип, который вы хотели передать конкретной функции.

Просто дополнение к ответу Джанхопанзы:

С помощью перегрузок функций вы также можете изменять количество аргументов, что может быть удобно.

Простой пример, если у вас есть какая-то функция со следующим объявлением:

Но вы часто вызываете foo с первым аргументом 0, вы можете написать следующую функцию, которая избавит вас от необходимости печатать.

Шаблоны (обычно) требуют, чтобы вы использовали идентичные синтаксис выполнять операции на всех (поддерживаемых) типах.

Перегрузка функций (или должна быть) используется аналогично, но позволяет использовать разные синтаксис для выполнения операций для разных типов. То есть (хотя вам и не нужно) вы можете представлять значения по-разному. Одним очевидным примером будет то, что называется atan а также atan2 в библиотеке C. С atan , мы передаем отношение «подъем к« пробегу », и мы возвращаем угол, который представляет отношение. atan2 мы передаем значения для подъема и запускаем по отдельности (который вычисляет примерно тот же результат, но так как он дает немного больше входных данных, может также дать более полный результат).

Хотя они реализованы как совершенно отдельные функции в C, если бы они были написаны на C ++ с самого начала, было бы совершенно уместно использовать одно имя (например, atan ) перегружены как по одному, так и по двум параметрам:

Шаблоны (если не считать специализации, которая в значительной степени просто переопределяет то, что предоставляют сами шаблоны), не предусматривает различий в вызове синтаксиса, подобного этому.

Перегрузка также более ограничена — вы предоставляете одну перегрузку для каждого конкретного типа, который вы хотите поддерживать (хотя, если вы берете указатель или ссылку, они также могут поддерживать производные типы). С шаблонами один шаблон может (по крайней мере потенциально) применяться к любой тип.

Добавить комментарий