funciones - lambda examples c++
C++ 11 std:: establecer la funciĆ³n de comparaciĆ³n lambda (6)
Desde mi experiencia jugando con el generador de perfiles, el mejor compromiso entre rendimiento y belleza es utilizar una implementación de delegado personalizada, como por ejemplo:
https://codereview.stackexchange.com/questions/14730/impossibly-fast-delegate-in-c11
Como la std::function
suele ser un poco demasiado pesada. No puedo comentar sobre sus circunstancias específicas, ya que no los conozco.
Quiero crear un std::set
con una función de comparación personalizada. Podría definirlo como una clase con operator()
, pero quería disfrutar de la capacidad de definir un lambda donde se usa, así que decidí definir la función lambda en la lista de inicialización del constructor de la clase que tiene el std::set
como miembro. Pero no puedo obtener el tipo de lambda. Antes de continuar, he aquí un ejemplo:
class Foo
{
private:
std::set<int, /*???*/> numbers;
public:
Foo () : numbers ([](int x, int y)
{
return x < y;
})
{
}
};
Encontré dos soluciones después de buscar: una, usando std::function
. Simplemente haga que el tipo de función de comparación establecida sea std::function<bool (int, int)>
y pase la lambda exactamente como lo hice. La segunda solución es escribir una función make_set, como std::make_pair
.
SOLUCIÓN 1:
class Foo
{
private:
std::set<int, std::function<bool (int, int)> numbers;
public:
Foo () : numbers ([](int x, int y)
{
return x < y;
})
{
}
};
SOLUCIÓN 2:
template <class Key, class Compare>
std::set<Key, Compare> make_set (Compare compare)
{
return std::set<Key, Compare> (compare);
}
La pregunta es, ¿tengo una buena razón para preferir una solución sobre la otra? Prefiero el primero porque hace uso de características estándar (make_set no es una función estándar), pero me pregunto: ¿el uso de std::function
hace que el código (potencialmente) sea más lento? Quiero decir, ¿reduce la posibilidad de que el compilador incorpore la función de comparación, o debería ser lo suficientemente inteligente como para comportarse exactamente como si fuera un tipo de función lambda y no de std::function
(lo sé, en este caso puede No seré un tipo lambda, pero ya sabes, lo estoy preguntando en general).
(Uso GCC, pero me gustaría saber qué hacen los compiladores populares en general)
RESUMEN, DESPUÉS DE TENER MUCHAS GRANDES RESPUESTAS:
Si la velocidad es crítica, la mejor solución es usar una clase con el operator()
aka functor. Es más fácil para el compilador optimizar y evitar cualquier indirección.
Para un mantenimiento sencillo y una mejor solución de uso general, utilizando las características de C ++ 11, use std::function
. Todavía es rápido (solo un poco más lento que el funtor, pero puede ser insignificante) y puede usar cualquier función - std::function
, lambda, cualquier objeto invocable.
También hay una opción para usar un puntero de función, pero si no hay un problema de velocidad, creo que std::function
es mejor (si usas C ++ 11).
Hay una opción para definir la función lambda en otro lugar, pero luego no se gana nada de que la función de comparación sea una expresión lambda, ya que también se puede convertir en una clase con operator()
y la ubicación de la definición no sería la construcción establecida de todas formas.
Hay más ideas, como usar la delegación. Si quieres una explicación más completa de todas las soluciones, lee las respuestas :)
La diferencia depende en gran medida de las optimizaciones de tu compilador. Si optimiza lambda en una std::function
son equivalentes, si no introduces una indirección en la primera que no tendrás en la última.
No es improbable que el compilador no pueda alinear una llamada std :: function, mientras que cualquier compilador que admita lambdas casi seguramente alineará la versión del functor, incluso si ese functor es un lambda no oculto por una std::function
.
Puede usar decltype para obtener el tipo de comparador lambda:
#include <set>
#include <iostream>
#include <iterator>
#include <algorithm>
int main()
{
auto comp = [](int x, int y){ return x < y; };
auto set = std::set<int,decltype(comp)>( comp );
set.insert(1);
set.insert(10);
set.insert(1); // Dupe!
set.insert(2);
std::copy( set.begin(), set.end(), std::ostream_iterator<int>(std::cout, "/n") );
}
Que impresiones:
1
2
10
Sí, una std::function
introduce una indirección casi inevitable a su set
. Mientras que el compilador siempre puede, en teoría, descubrir que todo el uso de la std::function
de su set
implica llamarlo a un lambda que siempre es exactamente el mismo lambda, que es duro y extremadamente frágil.
Frágil, porque antes de que el compilador pueda comprobarse a sí mismo que todas las llamadas a esa std::function
son en realidad llamadas a su lambda, debe demostrar que ningún acceso a su std::set
nunca la std::function
a nada que no sea su lambda . Lo que significa que tiene que rastrear todas las rutas posibles para llegar a su std::set
en todas las unidades de compilación y probar que ninguno de ellos lo hace.
Esto podría ser posible en algunos casos, pero los cambios relativamente inocuos podrían romperlo incluso si su compilador logró probarlo.
Por otro lado, un functor con un operator()
sin estado operator()
tiene un comportamiento fácil de probar, y las optimizaciones que involucran eso son cosas cotidianas.
Entonces sí, en la práctica, sospecho que la std::function
podría ser más lenta. Por otro lado, la solución std::function
es más fácil de mantener que make_set
one, y el intercambio de tiempo del programador para el rendimiento del programa es bastante fungible.
make_set
tiene la grave desventaja de que el tipo de dicho set
debe deducirse de la llamada a make_set
. A menudo, un set
almacena un estado persistente, y no algo que usted crea en la pila, entonces deja de estar fuera del alcance.
Si ha creado un Lambda auto MyComp = [](A const&, A const&)->bool { ... }
o sin estado global o estático auto MyComp = [](A const&, A const&)->bool { ... }
, puede usar la sintaxis std::set<A, decltype(MyComp)>
para crear un set
que puede persistir, sin embargo, es fácil para el compilador optimizar (porque todas las instancias de decltype(MyComp)
son funtores sin estado) y en línea. Señalo esto, porque estás pegando el set
en una struct
. (¿O tu compilador es compatible?
struct Foo {
auto mySet = make_set<int>([](int l, int r){ return l<r; });
};
que me parece sorprendente!)
Finalmente, si le preocupa el rendimiento, considere que std::unordered_set
es mucho más rápido (a costa de no poder iterar sobre los contenidos en orden, y tener que escribir / encontrar un buen hash), y que un std::vector
ordenado std::vector
es mejor si tiene un "insertar todo" de 2 fases y luego "consultar contenido repetidamente". Simplemente rellene el vector
primero, luego equal_range
erase
unique
, luego use el algoritmo libre de equal_range
.
Si está decidido a tener el set
como un miembro de la clase, inicializando su comparador en el momento del constructor, entonces, al menos, un nivel de indirección es inevitable. Considere que, por lo que sabe el compilador, podría agregar otro constructor:
Foo () : numbers ([](int x, int y)
{
return x < y;
})
{
}
Foo (char) : numbers ([](int x, int y)
{
return x > y;
})
{
}
Una vez que tiene un objeto de tipo Foo
, el tipo de set
no contiene información sobre qué constructor inicializó su comparador, por lo que para llamar al lambda correcto se requiere un direccionamiento indirecto al operator()
lambda seleccionado en tiempo de ejecución operator()
.
Dado que está utilizando lambdas sin captura, podría usar el tipo de puntero a función bool (*)(int, int)
como su tipo de comparación, ya que las lambdas sin captura tienen la función de conversión adecuada. Esto implicaría, por supuesto, una indirección a través del puntero a la función.
Un lambda sin estado (es decir, uno sin capturas) puede degradarse a un puntero de función, por lo que su tipo podría ser:
std::set<int, bool (*)(int, int)> numbers;
De lo contrario, make_set
solución make_set
. ¡Si no usa una función de creación de una línea porque no es estándar, no obtendrá mucho código escrito!