35

En la biblioteca Boost Signals, están sobrecargando el operador().¿Por qué anular el operador()?

¿Es esta una convención en C++? Para devoluciones de llamada, etc.?

He visto esto en el código de un compañero de trabajo (que es un gran fan de Boost). De toda la bondad de Boost, esto solo me ha llevado a la confusión.

¿Alguna idea del motivo de esta sobrecarga?

+1

Relacionados http://stackoverflow.com/questions/356950/c-functors-and-their-uses? – Konrad

Respuesta

100

Uno de los objetivos principales cuando se sobrecarga el operador() es crear un functor. Un functor actúa como una función, pero tiene las ventajas de que es estable, lo que significa que puede mantener los datos reflejando su estado entre llamadas.

Aquí está un ejemplo sencillo funtor:

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" 

Functors son muy utilizados en la programación genérica. Muchos algoritmos STL están escritos de una manera muy general, de modo que usted puede conectar su propia función/functor en el algoritmo. Por ejemplo, el algoritmo std :: for_each le permite aplicar una operación en cada elemento de un rango. Se podría implementar algo así:

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

se ve que este algoritmo es muy genérico, ya que está parametrizada por una función. Al usar el operador(), esta función le permite usar un functor o un puntero de función. He aquí un ejemplo que muestra las dos posibilidades:

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 

En cuanto a su pregunta sobre el operador() sobrecarga, pues sí es posible. Puede escribir perfectamente un functor que tenga varios operadores de paréntesis, siempre que respete las reglas básicas de sobrecarga de métodos (por ejemplo, no es posible sobrecargar el tipo de devolución).

+0

Creo que una gran parte de esta respuesta es la sintaxis del STL for_each. Al usar el operador() como la parte de operación del functor, funcionará bien con el STL. – JeffV

+0

Parece que si se implementó el STL como do() {...} en lugar de operator()() {...} se usaría do en su lugar. – JeffV

+3

Otra ventaja (usualmente menor) de los funtores sobre las funciones es que pueden ser trilialmente inlineados. No hay ninguna indirección de puntero involucrada, solo se llama a una función de miembro (no virtual) en una clase, por lo que el compilador puede determinar qué función se llama y enlinea eso. – jalf

1

Otro compañero de trabajo señaló que podría ser una forma de disfrazar los objetos del functor como funciones. Por ejemplo, esto:

my_functor(); 

es realmente:

my_functor.operator()(); 

lo hace quiere decir que esto:

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

Puede ser utilizado para sobrecargar esto también?

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

Su última línea no es una sobrecarga del operador en absoluto. Tiene que ser: ".operator() (int n, float f)" que se ve muy confuso la primera vez que lo ve. Puede sobrecargar este "operador de llamada de función" como si se tratara de otras funciones, pero no puede sobrecargarlo con la sobrecarga sin operador que ha especificado. – altruic

+0

@altruic, buen punto. Solo lo arregle. – JeffV

+0

Su segunda línea es incorrecta, en realidad es "my_functor.operator()();". my_functor.operator() es la referencia del método, mientras que el segundo conjunto de() denota la invocación. – eduffy

2

También puede consultar el C++ faq's Matrix example. Hay buenos usos para hacerlo, pero por supuesto depende de lo que estás tratando de lograr.

4

Un functor no es una función, por lo que no puede sobrecargarlo.
Su compañero de trabajo está en lo cierto al decir que la sobrecarga del operador() se usa para crear "funtores", objetos que se pueden llamar funciones similares. En combinación con plantillas que esperan argumentos "funcionales", esto puede ser bastante poderoso porque la distinción entre un objeto y una función se vuelve borrosa.

Como han dicho otros carteles: los funtores tienen una ventaja sobre las funciones simples en cuanto a que pueden tener estado. Este estado se puede usar en una única iteración (por ejemplo, para calcular la suma de todos los elementos en un contenedor) o en varias iteraciones (por ejemplo, para encontrar todos los elementos en múltiples contenedores que satisfagan determinados criterios).

18

Permite que una clase actúe como una función. Lo he usado en una clase de registro donde la llamada debería ser una función, pero quería el beneficio extra de la clase.

así que algo como esto:

logger.log("Log this message"); 

se convierte en esto:

logger("Log this message"); 
3

Comenzar a utilizar std::for_each, std::find_if, etc. con más frecuencia en el código y verá por qué es útil tener la capacidad de sobrecargar al operador(). También permite que los funtores y las tareas tengan un método de llamada claro que no entre en conflicto con los nombres de otros métodos en las clases derivadas.

2

Los funcionamientos son básicamente como punteros de función. En general, están destinados a ser copiables (como punteros de función) e invocados de la misma manera que los punteros de función. El principal beneficio es que cuando tienes un algoritmo que funciona con un functor con plantilla, la llamada de función al operador() puede estar en línea. Sin embargo, los punteros de función siguen siendo funtores válidos.

5

Muchos han respondido que se trata de un funtor, sin mencionar una de las razones por las que un functor es mejor que una simple función antigua.

La respuesta es que un funtor puede tener estado. Considere una función de suma: necesita mantener un total acumulado.

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

Eso no explica por qué hay una necesidad de ocultar el hecho de que es un objeto y enmascararlo como una función. – JeffV

+5

Jeff V: Conveniencia. Significa que se puede usar la misma sintaxis para hacer la llamada, ya sea que llamemos a un functor o un puntero de función. Si mira std :: for_each, por ejemplo, funciona con funtores o punteros a función, porque en ambos casos, la sintaxis de la llamada es la misma. – jalf

2

Una de las fortalezas que puedo ver, sin embargo esto se puede discutir, es que la firma del operador() se ve y se comporta de la misma manera en diferentes tipos. Si tuviéramos un reportero de clase que tenía un informe de método de miembro (...), y luego otro escritor de clase, que tenía un método de escritura de miembro (...), tendríamos que escribir adaptadores si quisiéramos usar ambas clases como quizás un componente de plantilla de algún otro sistema. Todo lo que importa es pasar cuerdas o lo que sea. Sin el uso del operador() sobrecargar o escribir adaptadores de tipo especial, no se podía hacer cosas como

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

debido T tiene una exigencia de que exista una función miembro llamada de escritura que acepta implícitamente nada moldeable a const char * (o más bien const char []). La clase Reporter en este ejemplo no tiene eso, por lo que tener T (un parámetro de plantilla) como Reporter no podría compilarse.

Sin embargo, en la medida que puedo ver que esto funcionaría con diferentes tipos

T t; 
t("Hello world"); 

embargo, todavía requiere explícitamente que el tipo T ha definido como un operador, por lo que todavía tienen un requisito en lo personal T. , No creo que sea demasiado extraño con los funtores ya que se usan comúnmente, pero preferiría ver otros mecanismos para este comportamiento. En idiomas como C#, podría pasar un delegado. No estoy muy familiarizado con los punteros de funciones de miembro en C++, pero podría imaginar que también podría lograr el mismo comportamiento allí.

Aparte del comportamiento del azúcar sintético Realmente no veo las fortalezas de la sobrecarga del operador para realizar tales tareas.

Estoy seguro de que hay más personas a sabiendas que tienen mejores razones que yo, pero pensé que podría exponer mi opinión para que el resto de ustedes la compartan.

+2

La ventaja de usar el operador() es que su parámetro de plantilla puede ser igualmente un puntero de función o un funtor. –

1

Otros mensajes han hecho un buen trabajo describiendo cómo funciona el operador() y por qué puede ser útil.

Recientemente he estado usando un código que hace un uso muy extenso de operator(). Una desventaja de sobrecargar este operador es que algunos IDEs se vuelven herramientas menos efectivas como resultado. En Visual Studio, por lo general, puede hacer clic con el botón derecho en una llamada a método para ir a la definición y/o declaración del método. Desafortunadamente, VS no es lo suficientemente inteligente como para indexar llamadas de operador(). Especialmente en código complejo con definiciones de operador() reemplazadas por todas partes, puede ser muy difícil determinar qué parte del código se está ejecutando. En varios casos, descubrí que tenía que ejecutar el código y rastrearlo para encontrar lo que realmente se estaba ejecutando.

Cuestiones relacionadas