что значит перегруженная функция с

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник

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

Возможно, кого-то из новичков пугает название темы “Перегрузка функций”? Меня пугало. Казалось – это что-то, с чем придется долго разбираться. На самом деле, перегрузку функций очень легко понять. Сложного нет ничего.

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

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

Определения перегруженных функций находятся в строках 31 – 61. В них создается переменная для накопления суммы значений элементов массива. В цикле for сумма накапливается. Далее полученное значение возвращается в программу, на место вызова перегруженной функции.

Обращение ко всем трём функциям производится по одному и тому-же имени – строки 17, 21, 25. Передав в функцию при вызове массив типа int и его размер, компилятор сам автоматически определит, какую из трёх перегруженных функций ему необходимо использовать. Точно так же произойдет и во время вызова остальных функций.

что значит перегруженная функция с. Смотреть фото что значит перегруженная функция с. Смотреть картинку что значит перегруженная функция с. Картинка про что значит перегруженная функция с. Фото что значит перегруженная функция с

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

Как делать не надо:

что значит перегруженная функция с. Смотреть фото что значит перегруженная функция с. Смотреть картинку что значит перегруженная функция с. Картинка про что значит перегруженная функция с. Фото что значит перегруженная функция с

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

Посмотрите короткое видео о перегрузке:

Источник

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

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

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

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

Элемент объявления функцииИспользование для перегрузки
Тип возвращаемого функцией значенияНет
Число аргументовДа
Тип аргументовДа
Наличие или отсутствие многоточияДа
Использование typedef именНет
Незаданные границы массиваНет
const или volatileДа, при применении ко всей функции
Квалификаторы refДа

Пример

В следующем примере показано использование перегрузки.

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

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

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

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

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

Точное соответствие найдено.

Тривиальное преобразование выполнено.

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

Стандартное преобразование в требуемый тип аргумента существует.

Пользовательское преобразование (оператор преобразования или конструктор) в требуемый тип аргумента существует.

Аргументы, представленные многоточием, найдены.

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

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

Рассмотрим следующий оператор.

Представленный выше оператор создает два набора.

Набор 1. Функции-кандидаты, имеющие первый аргумент дробного типаSet 2: функции-кандидаты, второй аргумент которого можно преобразовать в тип int
Variant 1Вариант 1 ( int можно преобразовать в long использование стандартного преобразования)
Variant 3

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

Пересечением этих двух наборов является функция Variant 1. Ниже представлен пример неоднозначного вызова функции.

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

Set 1: потенциальные функции, имеющие первый аргумент типа intSet 2: потенциальные функции с вторым аргументом типа int
Вариант 2 ( int можно преобразовать в long использование стандартного преобразования)Вариант 1 ( int можно преобразовать в long использование стандартного преобразования)

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

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

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

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

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

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

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

Выходные данные

Указатели const на volatile объекты и также считаются отличными от указателей на базовый тип в целях перегрузки.

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

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

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

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

Получающаяся последовательность преобразований (если таковые имеются), называется наилучшей последовательностью сопоставления. Существует несколько способов преобразования объекта типа int в тип unsigned long с помощью стандартных преобразований (см. описание в разделе int ).

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

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

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

Тип, из которого выполняется преобразованиеТип, в который выполняется преобразование
имя типаимя типа
имя типаимя типа
Type-Name[]имя типа
Type-Name(Argument-List)( Type-Name) (Argument-List)
имя типа const const
имя типа volatile volatile
имя типа const const *
имя типа volatile volatile *

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

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

От указателя к указателю на const ( type * to const type * ).

От указателя к указателю на volatile ( type * to volatile type * ).

Ссылка на ссылку на const ( type & to const type & ).

Ссылка на ссылку на volatile ( type & to volatile type & ).

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

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

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

Это же правило применяется для преобразований ссылок. Преобразование из типа D& в тип C& предпочтительнее преобразования из типа D& в тип B& и т. д.

Это же правило применяется для преобразований указателей на член. Преобразование из типа T D::* в тип T C::* предпочтительнее преобразования из типа T D::* в тип T B::* и т. д. ( T — тип члена.)

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

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

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

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

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

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

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

Обеим версиям Func требуется определенное пользователем преобразование для преобразования типа int в аргумент типа класса. Возможные преобразования:

Преобразование из типа int в тип UDC1 (определяемое пользователем преобразование).

Преобразование из типа int в тип long ; затем преобразование в тип UDC2 (преобразование из двух шагов).

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

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

Сопоставление аргументов и указатель this

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

Квалификаторы ref для функций-членов

Ограничения на перегрузку

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

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

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

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

Оператор New можно перегружать только на основе возвращаемого типа, а именно на основе указанного модификатора модели памяти.

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

Функции элементов не могут быть перегружены только на основе одного статического, а другого нестатического.

typedef Объявления не определяют новые типы; они представляют синонимы для существующих типов. Они не влияют на механизм перегрузки. Рассмотрим следующий код.

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

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

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

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

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

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

Если функция базового класса не объявлена как Virtual, то говорят, что функция производного класса скрывает ее. Переопределение и скрытие отличаются от перегрузки.

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

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

Вызов Deposit в Account::Deposit вызывает закрытую функцию члена. Этот вызов является правильным, так как Account::Deposit является функцией-членом и имеет доступ к закрытым членам класса.

Источник

Перегрузка в C++. Часть II. Перегрузка операторов

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

Оглавление

Введение

1. Общие вопросы перегрузки операторов

1.1. Перегружаемые операторы

1.2. Общие правила при выборе перегружаемого оператора

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

1.3. Операторы, не рекомендуемые для перегрузки

1.4. Интерфейс и семантика перегруженных операторов

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

1.5. Реализация перегрузки операторов

1.5.1. Два варианта реализации перегрузки операторов

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

1.5.2. Две формы использования перегруженных операторов

Использовать перегруженный оператор можно в двух формах (нотациях): инфиксной и функциональной. Инфиксная форма как раз и есть привычный синтаксис использования операторов.

Вот пример для класса из предыдущего раздела (будем считать, что код находится вне пространства имен N ):

Обратим внимание на то, что при использовании перегруженных операторов работает поиск, зависимый от типа аргумента (argument depended lookup, ADL), без него это использование, особенно в инфиксной форме, было бы весьма неудобно в случае, когда класс, для которого перегружается оператор, находится в другом пространстве имен. Вполне возможно, что ADL и появился в основном для решения этой проблемы.

1.5.3. Одновременное использование двух вариантов реализации перегрузки

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

2. Дополнительные подробности реализации перегрузки операторов

2.1. Множественная перегрузка

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

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

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

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

2.2.1. Симметрия

2.2.2. Расширение интерфейса класса

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

2.2.3. Неявные преобразования

2.2.4. Перечисления

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

2.3. Определение дружественной свободной функции внутри класса

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

Подробнее см. [Meyers1].

2.4. Вычислительные конструкторы

Если оператор возвращает объект по значению, иногда целесообразно определить специальный закрытый конструктор, называемый вычислительным конструктором (computational constructor). В этом случае компилятор сможет применить оптимизацию возвращаемого значения (return value optimization, RVO). Подробнее см. [Dewhurst].

2.5. Виртуальные операторы

2.6. Перегрузка операторов для перечислений

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

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

Перегрузим еще один оператор

Теперь перебрать все элементы перечисления можно с помощью стандартного алгоритма:

И еще один вариант. Определим класс:

После этого перебрать все элементы перечисления можно с помощью диапазонного for :

3. Особенности перегрузки некоторых операторов

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

3.2. Унарный оператор *

В стандартной библиотеке оператор * перегружен для интеллектуальных указателей и итераторов.

3.3. Оператор []

Индексатор часто перегружают в двух вариантах — константном и неконстантном.

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

3.3.1. Многомерные массивы

3.4. Оператор ()

3.4.1. Локальные определения и лямбда-выражения

В C++ нельзя определить функцию локально (в блоке). Но можно определить локальный класс и этот класс может быть функциональным. Столь популярные в народе лямбда-выражения как раз и представляют из себя средство для быстрого и удобного определения анонимного локального функционального класса на «на лету».

3.4.2. Мультифункциональные типы и объекты

3.4.3. Хеш-функция

В Приложении Б приводится пример решения для C-строк на основе полной специализации стандартного шаблона.

3.4.4. Сравнение элементов и ключей в контейнерах

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

В Приложении Б приводится пример решения для C-строк на основе полной специализации стандартного шаблона.

3.4.5. Удалители в интеллектуальных указателях

3.4.6. Алгоритмы

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

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

Пример для алгоритма сортировки C-строк приведен в Приложение Б.

3.4.7. Функциональный шаблон

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

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

3.6. Арифметические операторы

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

3.7. Инкремент, декремент

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

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

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

3.8. Операторы >

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

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

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

Среди операторов присваивания выделяются два стандартных — оператор копирующего присваивания и оператор перемещающего присваивания, которые соответствуют копирующему конструктору и перемещающему конструктору.

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

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

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

Если идиома «копирование и обмен» не используется, то необходима проверка на самоприсваивание.

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

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

X() уничтожает объект производного класса, что может полностью сломать взаимодействие базового класса и производного. Никогда так не делайте.

Оператор копирующего присваивания и оператор перемещающего присваивания (вместе с соответствующим конструктором) приходится перегружать практически всегда, когда нужна нестандартная семантика копирования/перемещения. (Запрет копирующего или перемещающего присваивания также можно рассматривать как перегрузку.) Также оператор присваивания обычно перегружается, как парный для конструктора с одним параметром. Практически все классы стандартной библиотеки перегружают операторы присваивания.

4. Итоги

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

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

Приложения

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

BinOper — это функциональный тип, совместимой с сигнатурой

Ключевое отличие BinOper от аналогичного в std::accumulate() — это то, что BinOper должен поддерживать несколько сигнатур:

Приложение Б. Хэш-функция и сравнение для C-строк

Функция hash_combine() — это хорошо известная функция из библиотеки Boost. Она может быть использована при создании других пользовательских хеш-функций.

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

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

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

Источник

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

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