Зачем переопределять operator ()?

В библиотеке Сигналы повышения они перегружают оператор ().

Это соглашение в C++? Для обратных вызовов и т. д.?

Я видел это в коде коллеги (который оказался большим поклонником Boost). Из всех имеющихся возможностей Boost это только привело меня в замешательство.

Есть какие-нибудь сведения о причине этой перегрузки?

Связанный stackoverflow.com/questions/356950/c-functors-and-their-uses?

Konrad 29.11.2010 20:10
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
62
1
64 222
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

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

my_functor();

Действительно:

my_functor.operator()();

Значит ли это:

my_functor(int n, float f){ ... };

Можно ли использовать и это для перегрузки?

my_functor.operator()(int n, float f){ ... };

Ваша последняя строка вообще не является перегрузкой оператора. Это должно быть: ".operator () (int n, float f)", что выглядит очень запутанным в первый раз, когда вы его видите. Вы можете перегрузить этот «оператор вызова функции», как и другие функции, но вы не можете перегрузить его указанной вами перегрузкой, не связанной с оператором.

altruic 25.11.2008 17:23

Ваша вторая строка неверна, это на самом деле "my_functor.operator () ();". my_functor.operator () - это ссылка на метод, а второй набор () обозначает вызов.

eduffy 25.11.2008 17:38

Вы также можете просмотреть Пример матрицы C++ faq. Для этого есть хорошие применения, но это, конечно, зависит от того, чего вы пытаетесь достичь.

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

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

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

так что-то вроде этого:

logger.log("Log this message");

превращается в это:

logger("Log this message");

Начните чаще использовать std::for_each, std::find_if и т. д. В своем коде, и вы поймете, почему удобно иметь возможность перегружать оператор (). Это также позволяет функторам и задачам иметь четкий метод вызова, который не будет конфликтовать с именами других методов в производных классах.

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

Многие ответили, что он создает функтор, но не объяснили, почему функтор лучше, чем простая старая функция.

Ответ в том, что у функтора может быть состояние. Рассмотрим функцию суммирования - ей нужно вести промежуточную сумму.

class Sum
{
public:
    Sum() : m_total(0)
    {
    }
    void operator()(int value)
    {
        m_total += value;
    }
    int m_total;
};

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

JeffV 25.11.2008 17:39

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

jalf 25.11.2008 19:04
Ответ принят как подходящий

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

Вот простой пример функтора:

struct Accumulator
{
    int counter = 0;
    int operator()(int i) { return counter += i; }
}
...
Accumulator acc;
cout << acc(10) << endl; //prints "10"
cout << acc(20) << endl; //prints "30"

Функторы широко используются в универсальном программировании. Многие алгоритмы STL написаны в очень общем виде, поэтому вы можете подключить к алгоритму свою собственную функцию / функтор. Например, алгоритм std :: for_each позволяет применить операцию к каждому элементу диапазона. Это можно было бы реализовать примерно так:

template <typename InputIterator, typename Functor>
void for_each(InputIterator first, InputIterator last, Functor f)
{
    while (first != last) f(*first++);
}

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

void print(int i) { std::cout << i << std::endl; }
...    
std::vector<int> vec;
// Fill vec

// Using a functor
Accumulator acc;
std::for_each(vec.begin(), vec.end(), acc);
// acc.counter contains the sum of all elements of the vector

// Using a function pointer
std::for_each(vec.begin(), vec.end(), print); // prints all elements

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

Я думаю, что большая часть этого ответа - синтаксис STL for_each. Использование operator () в качестве операционной части функтора будет хорошо работать с STL.

JeffV 25.11.2008 17:52

Похоже, что если бы STL был реализован как do () {...} вместо operator () () {...}, вместо этого использовался бы do.

JeffV 25.11.2008 17:56

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

jalf 25.11.2008 19:02

удалил мой комментарий о том, почему выбран именно оператор (), раз вы отредактировали это в своем сообщении :)

jalf 25.11.2008 20:03

'печатает «10 30» »: на самом деле он может напечатать« 20 30 ». Порядок оценки подвыражений не указан, за исключением того, что операнды каждого << должны быть оценены до того, как << будет (и, следовательно, из-за способа связи << определяется порядок, в котором печатаются части) . Ничего не сказано, что операнд последнего << должен оцениваться после операнда первого <<, просто последний << должен сам оцениваться после первого. Компилятору разрешено выполнить эту функцию acc (20) заранее.

Steve Jessop 20.01.2010 19:14

@jalf - разве operator() тоже не функция? Так как же у него могло быть преимущество при встраивании перед любой другой функцией? Единственный способ, которым это могло иметь значение, - это просто вопрос соглашения; функции-члены часто пишутся встроенными, в то время как автономные функции - нет. Это легко изменить, если вы знаете, что будете использовать автономную функцию в контексте, где встраивание было бы полезным.

Mark Ransom 27.10.2014 18:47

@MarkRansom да, это так. Однако подумайте о том, что компилятор видит в for_each в приведенном выше примере. Если я передам ему бесплатную функцию для третьего параметра, он создаст экземпляр for_each для типа int(*)(int). Во время компиляции это ничего не говорит нам о том, что вызывается. Он сообщает нам, что во время выполнения мы получим указатель, и этот указатель сообщит нам, какую функцию вызывать. Компилятор не знает, какая функция будет вызвана, поэтому он не может встроить вызов

jalf 28.10.2014 22:02

@MarkRansom Однако, если вы вместо этого передаете функтор, такой как Accumulator, определенный выше, то for_each создается для Accumulator, а функция, которая вызывается в его теле, называется Accumulator::operator()(int). Таким образом, компилятор знает, какая функция будет вызвана, независимо от фактического значения, передаваемого во время выполнения. Это позволяет тривиально встроить вызов

jalf 28.10.2014 22:02

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

Одна сильная сторона, которую я вижу, но это можно обсудить, заключается в том, что сигнатура operator () выглядит и ведет себя одинаково для разных типов. Если бы у нас был класс Reporter, у которого был метод-член report (..), а затем другой класс Writer, у которого был бы метод-член write (..), нам пришлось бы написать адаптеры, если бы мы хотели использовать оба класса как возможно компонент шаблона какой-либо другой системы. Все, о чем он будет заботиться, это передать строки или что у вас есть. Без использования перегрузки operator () или написания адаптеров специального типа вы не могли бы делать такие вещи, как

T t;
t.write("Hello world");

потому что T требует наличия функции-члена с именем write, которая принимает все, что может быть неявно приведено к const char * (или, скорее, к const char []). Класс Reporter в этом примере не имеет этого, поэтому, если T (параметр шаблона) является Reporter, не удастся скомпилировать.

Однако, насколько я понимаю, это будет работать с разными типами

T t;
t("Hello world");

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

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

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

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

Luc Touraille 27.11.2008 12:02

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

Недавно я использовал код, в котором очень широко используется оператор (). Недостатком перегрузки этого оператора является то, что в результате некоторые IDE становятся менее эффективными инструментами. В Visual Studio вы обычно можете щелкнуть правой кнопкой мыши вызов метода, чтобы перейти к определению и / или объявлению метода. К сожалению, VS недостаточно умен, чтобы индексировать вызовы operator (). Особенно в сложном коде с повсюду переопределенными определениями operator () может быть очень сложно выяснить, какой фрагмент кода где выполняется. В нескольких случаях я обнаружил, что мне нужно запустить код и проследить его, чтобы найти, что на самом деле выполняется.

Другие вопросы по теме