Что такое перегруженный метод

Перегрузка методов

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

Совершенно недостаточно, чтобы два метода отличались только типами возвращаемых значений. Они должны также отличаться типами или числом своих параметров. (Во всяком случае, типы возвращаемых значений дают недостаточно сведений компилятору C#, чтобы решить, какой именно метод следует использовать.) Разумеется, перегружаемые методы могут отличаться и типами возвращаемых значений. Когда вызывается перегружаемый метод, то выполняется тот его вариант, параметры которого соответствуют (по типу и числу) передаваемым аргументам.

Давайте рассмотрим пример использования перегрузки методов:

Что такое перегруженный метод. Смотреть фото Что такое перегруженный метод. Смотреть картинку Что такое перегруженный метод. Картинка про Что такое перегруженный метод. Фото Что такое перегруженный метод

Как видите метод ui перегружается три раза. Модификаторы параметров ref и out также учитываются, когда принимается решение о перегрузке метода. Несмотря на то что модификаторы параметров ref и out учитываются, когда принимается решение о перегрузке метода, отличие между ними не столь существенно. Давайте добавим еще одну перегрузку в вышеуказанный пример:

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

Допустим, что требуется функция, определяющая абсолютное значение. В языках, не поддерживающих перегрузку методов, обычно приходится создавать три или более вариантов такой функции с несколько отличающимися, но все же разными именами. Например, в С функция abs() возвращает абсолютное значение целого числа, функция labs() — абсолютное значение длинного целого числа, а функция fabs () — абсолютное значение числа с плавающей точкой обычной (одинарной) точности.

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

Чтобы закрепить понятие перегрузки методов, давайте рассмотрим перегрузку встроенного метода IndexOf класса String пространства имен System:

Что такое перегруженный метод. Смотреть фото Что такое перегруженный метод. Смотреть картинку Что такое перегруженный метод. Картинка про Что такое перегруженный метод. Фото Что такое перегруженный метод

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

Источник

Классы и объекты C#: перегрузка методов

О том, что такое методы C# и как их создавать мы уже поговорили, научились создавать методы классов и использовать их для доступа к свойствам. Однако, иногда бывает необходимо создать создать более одного метода с одним и тем же именем, но различным набором параметров. В практике программирования такой подход называется перегрузкой методов (method overloading).

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

Сигнатура метода C#

В C# сигнатура метода складывается из следующих элементов:

При этом при перегрузке метода его имя не меняется. Например,вернемся к нашему классу Building и создадим метод, который линейно увеличивает все три измерения здания (длину, ширину и высоту):

Перегрузка методов в C#

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

Также, мы можем поменять количество параметров:

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

Использование перегруженных методов

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

Что такое перегруженный метод. Смотреть фото Что такое перегруженный метод. Смотреть картинку Что такое перегруженный метод. Картинка про Что такое перегруженный метод. Фото Что такое перегруженный метод

Чтобы воспользоваться одной из перегрузок метода, мы можем полностью написать имя метода и, поставив после названия метода круглую скобку ( выбрать необходимую перегрузку из списка:

Что такое перегруженный метод. Смотреть фото Что такое перегруженный метод. Смотреть картинку Что такое перегруженный метод. Картинка про Что такое перегруженный метод. Фото Что такое перегруженный метод

Итого

Сегодня мы познакомились с такими понятиями как сигнатура метода C# и перегрузка методов. Перегрузка методов C# позволяет создавать в классе методы с одинаковым именем, но различающейся сигнатурой.

Источник

Перегруженные свойства и методы (Visual Basic)

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

Перегрузка использования

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

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

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

во время выполнения Visual Basic вызывает правильную процедуру на основе типов данных указанных параметров.

Перегрузка правил

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

Типы возвращаемых процедур

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

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

Если Overloads ключевое слово пропущено при перегрузке унаследованного члена с членом, имеющим идентичные параметры и типы параметров, перегрузка называется тенью по имени. Затенение по имени заменяет унаследованную реализацию члена и делает все другие перегрузки недоступными для экземпляров производного класса и его децедентс.

Overloads Shadows Модификаторы и не могут использоваться одновременно с одним свойством или методом.

Пример

В следующем примере создаются перегруженные методы, которые принимают String или Decimal представление суммы в долларе и возвращают строку, содержащую налог на продажу.

Использование этого примера для создания перегруженного метода

Добавьте в класс TaxClass приведенный далее код.

Добавьте следующую процедуру в форму.

Добавьте в форму кнопку и вызовите ShowTax процедуру из Button1_Click события кнопки.

Запустите проект и нажмите кнопку в форме, чтобы проверить перегруженную ShowTax процедуру.

Источник

Java Challengers #1: Перегрузка методов в JVM

У нас уже запустился очередной поток курса «Разработчик Java», но у нас ещё осталось немного материалов, которыми бы хотели с вами поделиться.

Добро пожаловать в серию статей Java Challengers! Этот серия статей посвящена особенностям программирования на Java. Их освоение — это ваш путь к становлению высококвалифицированным программистом на Java.

Освоение техник, рассматриваемых в этой серии статей требует некоторых усилий, но они будут иметь большое значение в вашем повседневном опыте в качестве java — разработчика. Избежать ошибок проще когда вы знаете как правильно применять основные техники программирования Java и отслеживать ошибки намного проще, когда вы точно знаете, что происходит в вашем java — коде.

Готовы ли вы приступить к освоению основных концепций программирования на Java? Тогда давайте начнем с нашей первой задачки!

Что такое перегруженный метод. Смотреть фото Что такое перегруженный метод. Смотреть картинку Что такое перегруженный метод. Картинка про Что такое перегруженный метод. Фото Что такое перегруженный метод

Термин «Перегрузка методов»

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

Что такое перегрузка методов?

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

В Листинге 1 показаны методы с разными параметрами, которые различаются количеством, типом и порядком.

Листинг 1. Три варианта перегрузки методов.

Перегрузка методов и примитивные типы

Таблица 1. Примитивные типы в Java

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

Использование перегрузки делает ваш код чище и проще для чтения, а также помогает избежать ошибок в программе.

Чем перегрузка не является

Помните, что изменение имени переменной не является перегрузкой. Следующий код не скомпилируется:

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

Перегрузка конструктора

Вы можете перегрузить конструктор таким же способом, как и метод:

Решите задачку по перегрузке методов

Готовы ли вы к первому испытанию? Давайте выясним!

Начните с внимательного изучения следующего кода.

Листинг 2. Сложная задача по перегрузке методов

Хорошо. Вы изучили код. Какой будет вывод?

Правильный ответ приведён в конце статьи.

Что сейчас произошло? Как JVM компилирует перегруженные методы

Для того чтобы понять что произошло в Листинге 2, вам нужно знать несколько вещей о том, как JVM компилирует перегруженные методы.

Прежде всего, JVM разумно ленива: она всегда будет прилагать наименьшие усилия для выполнения метода. Таким образом, когда вы думаете о том, как JVM обрабатывает перегрузку, имейте в виду три важных особенности компилятора:

Если вы никогда не сталкивались с этими техниками, то несколько примеров должны вам помочь их понять. Обратите внимание, что JVM выполняет их в том порядке, в котором они указаны.

Вот пример расширения:

Это порядок расширения примитивных типов:

Что такое перегруженный метод. Смотреть фото Что такое перегруженный метод. Смотреть картинку Что такое перегруженный метод. Картинка про Что такое перегруженный метод. Фото Что такое перегруженный метод

(Прим. переводчика — В JLS расширение примитивов описано с большими вариациями, например, long может быть расширен во float или в double.)

Обратите внимание, что происходит за кулисами при компиляции кода:

А вот пример распаковки:

Вот что происходит за кулисами при компиляции этого кода:

И вот пример метода с аргументами переменной длины. Обратите внимание, что методы переменной длины всегда являются последними для выполнения.

Что такое аргументы переменной длины?

Аргументы переменной длины — это просто массив значений, заданный трёмя точками (. ). Мы можем передать сколько угодно чисел int этому методу.

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

Расширение: практический пример

Аналогично, если мы передаём число 1.0 JVM автоматически распознает, что это double.

Распространенные ошибки с перегрузкой

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

Автоупаковка с обёртками (autoboxing with wrappers)

Java — это строго типизированный язык программирования и, когда мы используем автоупаковку с обёртками, есть несколько вещей, которые мы должны учитывать. Во-первых, следующий код не компилируется:

Автоупаковка будет работать только с типом double потому что, когда вы скомпилируете код, он будет эквивалентен этому:

Литералы чисел в коде

Как любопытный факт. Знаете ли вы, что тип char принимает числа?

Что необходимо помнить о перегрузке

Перегрузка — это очень мощная техника для случаев, когда вам нужно одинаковое имя метода с разными параметрами. Это полезная техника, потому что использование правильных имён делает код более удобным для чтения. Вместо того, чтобы дублировать имя метода и добавлять беспорядок в ваш код, вы можете просто перегрузить его.

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

Что следует иметь в виду: при перегрузке метода JVM сделает наименьшее усилие из возможных.

Вот порядок самого ленивого пути к исполнению:

На этом мы закончим о роли JVM в перегрузке методов. Важно понимать, что JVM по своей сути ленива, и всегда будет следовать по самому ленивому пути.

Ответ

Ответ к Листингу 2 — Вариант 3. efce.

Подробнее о перегрузке методов в Java

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

Узнайте больше о том, почему важно, что Java является строго типизированным языком и изучите примитивные типы Java.

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

Источник

Перегрузка в C++. Часть I. Перегрузка функций и шаблонов

Оглавление

Введение

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

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

1. Общие положения

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

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

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

Также надо учитывать, что компилятор выполняет некоторые стандартные преобразования типов параметров функций. Для типа массива выполняется сведение (decay) к указателю, поэтому

не перегруженные функции, это одно и то же.

Параметры типа функция сводятся к указателю на функцию.

Для параметров, передаваемых по значению, удаляется квалификатор const (и volatile ), поэтому

не перегруженные функции, это одно и то же.

1.2. Общая схема алгоритма поиска функции

В общих чертах алгоритм поиска функции можно описать следующим образом. На первом этапе компилятор осуществляет поиск (lookup) тех перегруженных функций, которые по правилам языка допустимы для данного вызова (candidate functions). В случае шаблонов выполняется еще вывод аргументов шаблона (template argument deduction). У этих функций количество параметров должно совпадать с количеством аргументов и тип аргументов должен совпадать с типом параметров (или существовать неявное преобразование типа аргументов к типу параметров). Если таких функций не найдено, поиск завершается ошибкой. Если найдена ровно одна функция, то поиск завершается успешно. Если найдено несколько функций, то начинается следующий этап, компилятор пытается выбрать ту, которая подходит «лучше всего» для данных аргументов (match the arguments most closely). Этот этап называется разрешением перегрузки (overload resolution). Если такая функция найдена, то разрешение перегрузки завершается успешно, иначе возникает ошибка (ambiguous call to overloaded function). Рассмотрим пример:

Для вызова Foo(«meow») ни одной подходящей функции не найдено, для вызова Foo(42) подходят обе функции, компилятор не может выбрать наиболее подходящую, а вот вызовы Foo(3.14f) и Foo(3.14) разрешаются успешно.

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

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

Но успешное разрешение перегрузки — это еще не все. После разрешения перегрузки производится проверка на доступность выбранной функции в точке вызова (то есть не является ли она private или protected ). В случае успеха производится проверка на удаленность (то есть не объявлена ли она как =delete ). Если эти проверки не проходят, компиляция завершается с ошибкой. Обратим внимание на то, что эти проверки никак не влияют на процедуру разрешения перегрузки, они всегда выполнятся после.

1.3. Текущая область видимости и разрешение перегрузки во вложенных областях видимости

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

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

1.3.1. Выбор текущей области видимости

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

В этом случае первоначальной текущей областью видимости будет глобальное пространство имен, объемлющих областей видимости нет.

Рассмотрим теперь «голые» вызовы функций без дополнительных квалификаторов класса или пространства имен.

Если такой вызов находится в пространстве имен, то это пространство имен и будет первоначальной текущей областью видимости, объемлющими областями видимости будут объемлющие пространства имен.

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

Пусть такой вызов находится в блоке

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

1.3.2. Разрешение перегрузки в классах

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

1.3.3. Локальное объявление функций

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

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

1.4. Расширение области видимости для разрешения перегрузки

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

1.4.1. Использования using-объявления в классе

Вот как это делается для предыдущего примера:

1.4.2. Использования using-объявления локально и в пространстве имен

1.4.3. Использования using-директивы

1.4.4. Поиск, зависимый от типа аргументов

Есть одна ситуация, когда компилятор самостоятельно расширяет текущую область видимости для разрешения перегрузки. Рассмотрим объявление класса и функции в некотором пространстве имен:

Рассмотрим код (вне пространства имен N ):

2. Некоторые правила разрешения перегрузки

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

2.1. Неявные преобразования типа и параметры «близкого» типа

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

Но для таких перегруженных функций

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

Семантически и побитово совпадающие типы, например, int и long также различаются при разрешении перегрузки.

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

2.2. Нулевой указатель

В C++98 приходилось писать

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

2.3. Универсальная инициализация и списки инициализации

Подробнее про универсальную инициализацию можно почитать у Скотта Мейерса [Meyers2].

2.4. Функции с переменным числом параметров

2.5. Шаблоны функций

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

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

2.5.1. Общие правила перегрузки

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

Если выбрана конкретизация шаблона, то проверяется, нет ли полной специализации этого шаблона для выведенного типа аргумента конкретизации. Если такая специализация есть, то выбирается она. Обратим внимание на то, что полные специализации рассматриваются в последнюю очередь, после выбора шаблона. Подробнее про описанный алгоритм разрешения перегрузки можно почитать у Герба Саттера [Sutter2].

В C++11 появились шаблоны с переменным количеством параметров или вариативные шаблоны (variadic templates). Если для некоторого вызова допустимыми являются конкретизации вариативного шаблона и обычного, то последний всегда будет считаться более специализированным и, соответственно, выбран при разрешении перегрузки.

2.5.2. Принцип SFINAE

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

В этом случае, если в текущей области видимости есть перегруженные шаблоны, для которых аргументы выведены успешно, или перегруженные нешаблонные функции, то ошибки не возникает, такой шаблон просто «молча» исключается из разрешения перегрузки. Это и называется принципом SFINAE, который расшифровывается как Substitution Failure is not an Error (сбой при подстановке не является ошибкой).

2.5.3. Пример разрешения перегрузки

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

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

2.5.4. Управление перегрузкой шаблонов

Рассмотрим перегруженные функции и шаблоны:

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

Ну и, наконец, варианты с использованием условных инструкций и операторов, вообще без использования перегрузки:

2.6. Правила разрешения перегрузки для параметров «родственного» типа

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

Для описания этих правил необходимо использовать так называемые категории аргументов. Для нашего уровня детализации достаточно использовать четыре категории:

Обе константные категории часто можно рассматривать как единую категорию — константы.

Рассмотрим теперь, допустимые категории аргументов для рассматриваемых типов параметров.
Пусть параметр имеет тип ссылки:

В этом случае допустимой категорией аргументов будет только lvalue.

Пусть параметр имеет тип rvalue-ссылки:

В этом случае допустимой категорией аргументов будет только rvalue.

Пусть параметр имеет тип ссылки на константу или сам тип:

В этих случаях допустимы любые категории аргументов.

2.6.1. Передача параметров по ссылке, ссылке на константу и по значению

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

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

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

Здесь для констант и rvalue будет выбрана вторая функция, а вот для lvalue выбор будет неоднозначный.

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

Для любых аргументов выбор будет неоднозначный.

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

Возможность модифицировать rvalue объект может показаться несколько странной и даже бессмысленной. Но это не совсем так, иногда ее можно с пользой использовать. В данном примере демонстрируется известная идиома полной очистки объекта с помощью rvalue объекта и функции обмена состояниями. (Ну и не надо забывать, что вся семантика перемещения базируется на модификации rvalue объекта.) Но вообще модификация rvalue объекта может создать всякого рода проблемы. Для того, чтобы предотвратить это, у функций, которые возвращают объект по значению, тип возвращаемого значения объявляют константным. Подробнее об этом можно почитать у Герба Саттера [Sutter1].

2.6.2. Rvalue ссылки

Одно из самых значительных нововведений C++11 является семантика перемещения. Для ее реализации был введен специальный тип — rvalue-ссылка. Rvalue-ссылки это разновидность обычных C++ ссылок, отличие состоит в правилах инициализации и правилах разрешения перегрузок функций, имеющих параметры типа rvalue-ссылка. Программист должен четко знать описанные ниже правила, иначе результат перегрузки может оказаться неожиданным для программиста, компилятор «молча» заменит перемещение на копирование и все преимущества перемещения будут утеряны.

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

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

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

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

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

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

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

2.6.3. Универсальные ссылки

3. Другие темы, связанные с перегрузкой

3.1. Параметры неполного типа

В C++ в ряде случаев компилятору достаточно знать, что то или иное имя является именем какого-то пользовательского типа (класса, структуры, объединения, перечисления), а полное объявление типа не нужно. В этом случае можно использовать неполное объявление (incomplete declaration), называемое еще упреждающим (forward declaration). Типы с неполным объявлением называются неполными. Механизм перегрузки работает и для неполных типов.

В данном случае полное объявление класса X может быть недоступно, но разрешение перегрузки работает.

3.2. Инициализация указателя на функцию

При инициализации указателя на функцию также можно использовать перегруженные функции.

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

Преобразования типа также способны выбирать из перегруженных функций.

Здесь также требуется точное совпадение параметров и возвращаемого значения и ADL не работает.

Перегруженные функции-члены также можно использовать при инициализации указателей на функции-члены класса.

3.3. Перегрузка и параметры по умолчанию

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

можно заменить на единственную функцию с параметром по умолчанию

В случае перегруженных функций этот код был бы корректным.

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

3.4. Перегрузка виртуальных функций

К перегрузке виртуальных функций надо относиться с осторожностью. Дело в том, что разрешение перегрузки выполняется на этапе компиляции, и, соответственно, используется статический тип переменной, для которой вызывается виртуальная функция. Это не очень хорошо согласуется с динамической природой виртуальных функций и может привести к неприятным неожиданностям («потерей» наследуемых функций базового класса, см. раздел 1.3). Подробнее ситуация описана в [Dewhurst]. Но относиться с осторожностью — это не значит не использовать совсем. Если мы проектируем полиморфную иерархию классов, в корне которой находится интерфейсный класс (абстрактный класс, у которого почти все функции-члены чисто виртуальные), все перегрузки сделаны в этом классе и доступ к производным классам осуществляется только через этот интерфейсный класс, то никаких неприятностей не будет. Подобная модель построения полиморфной иерархии классов используется весьма широко. В Приложении А мы покажем, как перегрузка виртуальных функций используется при реализации известного паттерна проектирования Visitor.

3.5. Метапрограммирование

Разрешение перегрузки происходит на этапе компиляции, поэтому не удивительно, что этот механизм активно используется в метапрограммировании — программировании кода, который выполняется на этапе компиляции. Метапрограммирование активно используется при написании шаблонов, в том числе и шаблонов стандартной библиотеки. Без его использования практически невозможно написать универсальные, гибкие и эффективные шаблоны.

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

Ну а там, где появляются разные типы, можно использовать перегрузку.

Вот еще один прием, используемый в метапрограммировании. Рассмотрим выражение:

Это выражение вычисляется во время компиляции. При этом expr не вычисляется, определяется только его тип. После этого выполняется разрешение перегрузки, но сама Foo не вызывается, определяется только возвращаемый тип и поэтому определение Foo не нужно. Таким образом перегрузка используется для отображения типа на числовое значение.

4. Итоги

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

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

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

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

Приложения

Приложение А. Двойная диспетчеризация и паттерн Visitor

во всех производных классах переопределяется одинаково, тело состоит из одной инструкции:

Voila. Двойная диспетчеризация готова.

Приложение Б. Подмена стандартных функций пользовательскими версиями

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

1. Определить в классе функцию-член Swap() (имя не принципиально), реализующую обмен состояниями.

2. В том же пространстве имен, что и класс X (обычно в том же заголовочном файле, а иногда и в теле класса), определить свободную (не-член) функцию swap() следующим образом (имя и сигнатура принципиальны):

После этого, благодаря ADL, эта функция сможет участвовать в разрешении перегрузки вместе с std::swap() и в этом случае будет выбрана как имеющая лучшее соответствие.

3. Определить полную специализацию std::swap() для X

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

Пусть теперь обмен состояниями делается так:

В этом варианте пользовательская swap() не будет рассматриваться и вот тут и придет на помощь полная специализация std::swap() — будет выбрана она. Но это тоже не вполне правильный вариант.

А совсем правильный вариант такой:

Но полная специализация поможет, если в стандартной библиотеке (то есть в пространстве имен std ) по ошибке используется

Скотт Мейерс [Meyers1] утверждает, что в стандартной библиотеке такую ошибку полностью исключить нельзя.

Рассмотрим теперь случай, когда функцию обмена состояниями надо определить для шаблона класса.

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

С помощью ключевого слова friend определение swap() можно перенести внутрь шаблона:

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

Список литературы

[GoF]
Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования.: Пер. с англ. — СПб.: Питер, 2001.
[Dewhurst]
Дьюхэрст, Стефан К. Скользкие места C++. Как избежать проблем при проектировании и компиляции ваших программ.: Пер. с англ. — М.: ДМК Пресс, 2012.
[Meyers1]
Мэйерс, Скотт. Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ.: Пер. с англ. — М.: ДМК Пресс, 2014.
[Meyers2]
Мейерс, Скотт. Эффективный и современный C++: 42 рекомендации по использованию C++11 и C ++14.: Пер. с англ. — М.: ООО «И.Д. Вильямс», 2016.
[Sutter1]
Саттер, Герб. Решение сложных задач на C++.: Пер. с англ. — М: ООО «И.Д. Вильямс», 2015.
[Sutter2]
Саттер, Герб. Новые сложные задачи на C++.: Пер. с англ. — М: ООО «И.Д. Вильямс», 2015.

Источник

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *