Support operator что это
Перегрузка операторов на C++
В этом руководстве мы узнаем о перегрузке операторов с помощью примеров. В C++ мы можем изменить способ работы операторов для определенных пользователем типов, таких как объекты и структуры. Это известно, как перегрузка оператора. Например:
Предположим, мы создали три объекта c1, c2 и result класса с именем Complex, который представляет комплексные числа.
Поскольку перегрузка операторов позволяет нам изменить способ работы операторов, мы можем переопределить принцип работы оператора + и использовать его для сложения комплексных чисел c1 и c2, написав следующий код:
Это делает наш код интуитивно понятным и легким для понимания.
Примечание. Мы не можем использовать перегрузку операторов для фундаментальных типов данных, таких как int, float, char и т.д.
Синтаксис для перегрузки оператора
Чтобы перегрузить оператор, мы используем специальную операторную функцию.
Примечание: когда мы перегружаем операторы, мы можем использовать их для работы любым удобным нам способом. Например, мы могли бы использовать ++ для увеличения значения на 100.
Однако это делает наш код запутанным и трудным для понимания. Наша задача как программиста – правильно, последовательно и интуитивно использовать перегрузку операторов.
Приведенный выше пример работает, только когда ++ используется в качестве префикса. Чтобы заставить ++ работать как постфикс, мы используем этот синтаксис.
Обратите внимание на int в круглых скобках. Это синтаксис для использования унарных операторов в качестве постфикса, это не параметр функции.
Пример 2
Пример 2 работает, когда ++ используется и как префикс, и как постфикс. Однако это не сработает, если мы попытаемся сделать что-то вроде этого:
Это связано с тем, что тип возвращаемого значения нашей функции operator недействителен. Мы можем решить эту проблему, сделав Count в качестве возвращаемого типа функции operator.
Пример 3: возвращаемое значение из функции оператора
Здесь мы использовали следующий код для перегрузки префиксного оператора:
Код для перегрузки постфиксного оператора тоже такой же. Обратите внимание, что мы создали объект temp и вернули его значение операторной функции.
Также обратите внимание на код:
В бинарных операторах
Бинарные операторы в си++ работают с двумя операндами. Например:
Здесь + – это бинарный оператор, который работает с операндами num и 9.
Когда мы перегружаем бинарный оператор для определяемых пользователем типов с помощью кода:
Операторная функция вызывается с использованием объекта obj1, а obj2 передается в качестве аргумента функции.
Пример 4: перегрузка двоичного оператора
В этой программе операторная функция:
Вместо этого мы также могли бы написать эту функцию как:
Инструкция по обучению оператора поддержки
Все мы оказались в ситуации, когда многие процессы экстренно переводятся в статус онлайн. И работа поддержки в этот момент особенно важна. Операторам нужно обрабатывать гораздо больше запросов пользователей и не сойти с ума.
Мы поговорили с Ириной, Customer Support Team Lead в Carrot quest, о том, как она обучает своих операторов. Ирина рассказала о секретах и лайфхаках, которые помогли ей создать суперкоманду поддержки, готовую к любым ситуациям в мире. А мы нежадные — делимся с вами!
Операторам обязательно надо давать ошибаться, чтобы они на себе опробовали какие-то кейсы и не боялись браться за сложные задачи. Это бывает больно и тяжело для клиентов, но по-другому нельзя научиться.
Короче, как со врачами.
С чего начать
Дайте новичку реальный кейс
Новичку первым делом полезно взять реального пользователя и пройти вместе путь работы с сервисом, провести аккаунтинг. В такой ситуации оператор разберется, к кому идти за конкретной информацией, поймет, что не получается, какие кейсы пользователь хочет внедрять и с какими проблемами сталкивается. А вы сможете проанализировать, на что способен новый сотрудник и какие навыки нужно прокачать. Не забудьте предупредить пользователя, что с ним работает новичок.
С самого начала своего обучения я взяла на внедрение одного клиента, который очень много хотел, но не хотел платить за это деньги. Я его предупредила, что новенькая и ничего не знаю, но если что, могу спросить, где надо. Это был клиент из e-commerce, и вместе с ним мы прошли основные кейсы. То же самое я предлагаю делать ребятам.
Если давать новичку типичные задания или уже проработанные кейсы, то у оператора не будет мотивации и конечной цели. Такие кейсы не несут нужной пользы, так как искусственно созданы и оторваны от клиента. Рекомендуем на начальном этапе созваниваться и пытаться в реальном разговоре помочь клиенту. Таким образом новичок тренирует навык реального общения, учится решать проблему и находить нужную информацию.
Структурируйте теорию
Уделите внимание теоретической части обучения. Не вываливайте на нового сотрудника всю информацию, которую он должен знать — дайте ему возможность свободно распоряжаться временем для изучения материала. Установите контрольное время, когда будете в формате диалога проверять изученный материал и насколько оператор его усвоил.
В команде поддержки Carrot quest основные теоретические понятия оформлены карточками в трелло. Одна карточка — одна неделя. На каждую неделю дается несколько тем со ссылками на статьи, которые нужно изучить. Таким образом оператор может переходить от одной темы к другой по желанию.
Как тренировать
После теоретического этапа дайте оператору порешать кейсовые вопросы из диалогов техподдержки. Возьмите рандомный диалог либо дайте несложные закрытые диалоги. Цель оператора — не испугаться и довести диалог до конца, чтобы пользователь поблагодарил и ушел довольный. По каждому такому кейсу новичку нужно вести подробный дневник, чтобы вы могли проанализировать и обсудить плюсы и минусы.
Ведение дневника в трелло помогло мне систематизировать знания. Я мог вернуться к пройденным кейсам и применить их на новых случаях, поизучать кейсы более детально.
Для помощи в решении готовых кейсов используйте базы знаний. Поможет внешняя база знаний для клиентов и внутренняя техническая база знаний. Не требуйте от новичка зубрить всю теорию — ему нужно научиться время от времени к ней обращаться.
Цель таких кейсов не просто вложить в оператора все знания разом, а научить ориентироваться в информации, чтобы он знал, к кому можно обратиться и в какой программе найти ответ. Но быстро это не получится: такой навык придет только с опытом.
Мне кажется, что бессмысленно без контекста вдалбливать в человека такую информацию, потому что вряд ли он что-то запомнит. Оператор должен иметь возможность вернуться к кейсам, которые он отработал, оставлять себе подсказки и автоматизировать поиск информации.
Оператору в IT-сфере нужно ориентироваться в скриптах, языках программирования, понимать проблемы клиентов. В этом помогают бесплатные ресурсы: в интернете много курсов, статей. Чтобы понять особенность продуктовой среды, Ирина рекомендует прочитать «Спроси маму: Как общаться с клиентами и подтвердить правоту своей бизнес-идеи, если все кругом врут?» Роберта Фитцпатрика.
Когда новичок не может решить проблему самостоятельно, не отказывайте ему в помощи. Пообещайте помочь, но пусть оператор сначала расскажет, как попытался сам решить этот вопрос. Во время такой рефлексии человек поймет, что где-то сам не добрал, не поискал, и таким образом научится решать проблемы.
Сколько времени нужно обучать
Срок онбординга зависит от того, на какую позицию идет человек. Если это стандартный дневной оператор, который будет самостоятельно решать вопросы клиентов, среднее время обучения — 3 месяца:
Не увлекайтесь контролем за новичком: не допускайте ситуации, что на протяжении 3 месяцев тимлид сидит над человеком и проверяет, как он работает. Оператор должен желать искать новое и стремиться к самостоятельности.
Я за максимальную самостоятельность, иначе человек привыкнет работать под контролем. Должно быть желание обучаться.
Как проверять оператора
Как следить за ростом оператора
Можно прокачивать какие-то отдельные навыки, но индивидуально, так как у сотрудника должна быть внутренняя мотивация. Если погружение в новые задачи проходит быстро и комфортно, то можно давать что-то новое, а если нет, то нагружать не стоит. Расписать на три года вперед развитие сотрудника невозможно.
Я сама в последнее время развиваюсь с поступлением задач. Занимаюсь саморефлексией: анализирую, как формулирую задачи ребятам, как я с ними общаюсь, как распределяю время на задачи, достаточно ли я отдыхаю, чтобы быть продуктивной в течение рабочего дня. Новые знания приходят постоянно практически без моего участия.
Вы можете следить, как операторы решают задачи. Но в то же время не создавайте атмосферу постоянного контроля. Стоит допустить не совсем идеальное решение, чтобы у человека был прогресс.
Для разнообразия операторы могут заниматься индивидуальными проектами. В команде саппорта Carrot quest Вероника пишет статьи в базу знаний и для Dashly, помогает с бухгалтерией. Аксинья работает с оттоком и отвечает за финансовые вопросы, Илья пишет статьи для Dashly и занимается документацией. А еще есть Игорь, он может все. Каждый из них хорош в своем деле, потому что главное — давать проекты, которые будут интересны сотрудникам.
Если человеку не нравится работать над задачей, ты не заставишь ее сделать хорошо. Разово, конечно, можно заставить, но в долгосрочной перспективе это ни к чему хорошему не приведет.
Основные правила
Берите реальный проект и проходите все ошибки с пользователем, который предупрежден об операторе-новичке.
Изначально старайтесь пропускать некритичные ошибки. Новичку может быть тяжело от множества нюансов, в которых ему пытаются помочь. Наличие критики и обучения должно быть дозировано.
В этапах обучения следуйте от обсуждения крупных недочетов к мелким, шлифовку отложите на последний момент, дайте человеку адаптироваться.
Не пытайтесь все делать за сотрудника. Максимально быстро дайте возможность креативить и самостоятельно придумывать решения. Если будете все делать за человека, он может просто привыкнуть. Пусть сотрудник самостоятельно рассчитывает время на свои задачи.
Как это работает в Carrot quest
Мы описали решения, которые действуют в команде поддержки Carrot quest. Если сомневаетесь в эффективности этих методов, посмотрите наши показатели за февраль:
Ну а лучший показатель — это отзывы клиентов:
Екатерина Оборина
Контент-маркетолог Carrot quest
Перегрузка в 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.