В библиотеке Сигналы повышения они перегружают оператор ().
Это соглашение в C++? Для обратных вызовов и т. д.?
Я видел это в коде коллеги (который оказался большим поклонником Boost). Из всех имеющихся возможностей Boost это только привело меня в замешательство.
Есть какие-нибудь сведения о причине этой перегрузки?





Другой сотрудник указал, что это может быть способ замаскировать объекты-функторы под функции. Например, это:
my_functor();
Действительно:
my_functor.operator()();
Значит ли это:
my_functor(int n, float f){ ... };
Можно ли использовать и это для перегрузки?
my_functor.operator()(int n, float f){ ... };
Ваша последняя строка вообще не является перегрузкой оператора. Это должно быть: ".operator () (int n, float f)", что выглядит очень запутанным в первый раз, когда вы его видите. Вы можете перегрузить этот «оператор вызова функции», как и другие функции, но вы не можете перегрузить его указанной вами перегрузкой, не связанной с оператором.
Ваша вторая строка неверна, это на самом деле "my_functor.operator () ();". my_functor.operator () - это ссылка на метод, а второй набор () обозначает вызов.
Вы также можете просмотреть Пример матрицы 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;
};
Это не объясняет, почему необходимо скрывать тот факт, что это объект, и маскировать его под функцию.
Джефф Ви: Удобство. Это означает, что для вызова можно использовать один и тот же синтаксис, независимо от того, вызываем ли мы функтор или указатель на функцию. Например, если вы посмотрите на std :: for_each, он работает либо с функторами, либо с указателями на функции, потому что в обоих случаях синтаксис вызова одинаков.
Одна из основных целей при перегрузке 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.
Похоже, что если бы STL был реализован как do () {...} вместо operator () () {...}, вместо этого использовался бы do.
Другое (обычно незначительное) преимущество функторов перед функциями состоит в том, что они могут быть тривиально встроены. Здесь нет косвенного указания указателя, просто вызывается (невиртуальная) функция-член класса, поэтому компилятор может определить, какая функция вызывается, и встроить ее.
удалил мой комментарий о том, почему выбран именно оператор (), раз вы отредактировали это в своем сообщении :)
'печатает «10 30» »: на самом деле он может напечатать« 20 30 ». Порядок оценки подвыражений не указан, за исключением того, что операнды каждого << должны быть оценены до того, как << будет (и, следовательно, из-за способа связи << определяется порядок, в котором печатаются части) . Ничего не сказано, что операнд последнего << должен оцениваться после операнда первого <<, просто последний << должен сам оцениваться после первого. Компилятору разрешено выполнить эту функцию acc (20) заранее.
@jalf - разве operator() тоже не функция? Так как же у него могло быть преимущество при встраивании перед любой другой функцией? Единственный способ, которым это могло иметь значение, - это просто вопрос соглашения; функции-члены часто пишутся встроенными, в то время как автономные функции - нет. Это легко изменить, если вы знаете, что будете использовать автономную функцию в контексте, где встраивание было бы полезным.
@MarkRansom да, это так. Однако подумайте о том, что компилятор видит в for_each в приведенном выше примере. Если я передам ему бесплатную функцию для третьего параметра, он создаст экземпляр for_each для типа int(*)(int). Во время компиляции это ничего не говорит нам о том, что вызывается. Он сообщает нам, что во время выполнения мы получим указатель, и этот указатель сообщит нам, какую функцию вызывать. Компилятор не знает, какая функция будет вызвана, поэтому он не может встроить вызов
@MarkRansom Однако, если вы вместо этого передаете функтор, такой как Accumulator, определенный выше, то for_each создается для Accumulator, а функция, которая вызывается в его теле, называется Accumulator::operator()(int). Таким образом, компилятор знает, какая функция будет вызвана, независимо от фактического значения, передаваемого во время выполнения. Это позволяет тривиально встроить вызов
Использование 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 () заключается в том, что ваш параметр шаблона может быть в равной степени указателем на функцию или функтором.
В других сообщениях хорошо описано, как работает operator () и почему он может быть полезен.
Недавно я использовал код, в котором очень широко используется оператор (). Недостатком перегрузки этого оператора является то, что в результате некоторые IDE становятся менее эффективными инструментами. В Visual Studio вы обычно можете щелкнуть правой кнопкой мыши вызов метода, чтобы перейти к определению и / или объявлению метода. К сожалению, VS недостаточно умен, чтобы индексировать вызовы operator (). Особенно в сложном коде с повсюду переопределенными определениями operator () может быть очень сложно выяснить, какой фрагмент кода где выполняется. В нескольких случаях я обнаружил, что мне нужно запустить код и проследить его, чтобы найти, что на самом деле выполняется.
Связанный stackoverflow.com/questions/356950/c-functors-and-their-uses?