Почему функторы C ++ могут быть предпочтительнее объектов с именованными методами? - PullRequest
7 голосов
/ 05 апреля 2011

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

Есть ли что-то особенное в перегрузке оператора () или это только немного более синтаксически привлекательнее, чем использование обычных именованных методов?

Обновление:

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

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

Ответы [ 3 ]

6 голосов
/ 05 апреля 2011

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

Есть несколько причин для перегрузки operator() вместо использования функции - но самая важная из них заключается в том, что компиляторы редко оптимизируют косвенный вызов функции при использовании указателя функции, но почти всегда оптимизируют operator() вызов - вот почему std::sort обычно бьет std::qsort.

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

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

Тогда это уже не функтор. Либо передайте два функтора, чтобы сделать то, что вы хотите, либо определите шаблонный метод class. (Вы также можете использовать миксины для получения шаблона шаблонного метода в C ++ без дополнительных затрат времени выполнения - но статья в Википедии не охватывает это) (Примечание также: не то же самое, что шаблоны C ++, хотя шаблоны C ++ могут быть задействованы, если вы перейдете к Маршрут АОП)

1 голос
/ 05 апреля 2011

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

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

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

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

Разделенным примером является хеш-таблица, для которой требуется компаратор равенства и хеш-функция.В этом случае две функции могут быть не связаны: обернуть класс operator==() для равенства и обернуть свободную функцию для вычисления хеша.

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

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

0 голосов
/ 05 апреля 2011

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

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

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

...