Почему переопределить operator()?

В библиотеке Библиотека ускорения они перегружают оператор().

Является ли это конвенцией в С++? Для обратных вызовов и т.д.?

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

Любое понимание причины этой перегрузки?

Ответы

Ответ 1

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

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

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

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

Ответ 2

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

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

logger.log("Log this message");

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

logger("Log this message");

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

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

Ответ 6

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

Ответ 7

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

Ответ 9

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

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

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

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

T t;
t("Hello world");

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

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

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

Ответ 10

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

my_functor();

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

my_functor.operator()();

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

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

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

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

Ответ 11

Другие должности хорошо проработали, описывая работу оператора() и почему это может быть полезно.

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