77 votes

Fonction de comparaison std :: set lambda C ++11

Je veux créer un std::set avec une fonction de comparaison personnalisée. Je pourrait la définir comme une classe avec l'opérateur(), mais je voulais profiter de la possibilité de définir un lambda où il est utilisé, j'ai donc décidé de définir la fonction lambda dans la liste d'initialisation du constructeur de la classe qui a le std::set en tant que membre. Mais je ne peux pas obtenir le type de la lambda. Avant de poursuivre, voici un exemple:

class Foo
{
private:
     std::set<int, /*???*/> numbers;
public:
     Foo () : numbers ([](int x, int y)
                       {
                           return x < y;
                       })
     {
     }
};

J'ai trouvé deux solutions après une recherche: l'un, à l'aide de std::function. Juste le jeu de la comparaison de la fonction de type std::function<bool (int, int)> , et de transmettre le lambda exactement comme je l'ai fait. La deuxième solution est d'écrire un make_set fonction, comme std::make_pair.

SOLUTION 1:

class Foo
{
private:
     std::set<int, std::function<bool (int, int)> numbers;
public:
     Foo () : numbers ([](int x, int y)
                       {
                           return x < y;
                       })
     {
     }
};

SOLUTION 2:

template <class Key, class Compare>
std::set<Key, Compare> make_set (Compare compare)
{
     return std::set<Key, Compare> (compare);
}

La question est, dois-je avoir une bonne raison de préférer une solution plutôt que l'autre? Je préfère la première, car elle rend l'utilisation de caractéristiques de série (make_set n'est pas une fonction standard), mais je me demande: est-ce à l'aide de std::function rendre le code (potentiellement) plus lent? Je veux dire, est-il baisser la chance le compilateur inlines la fonction de comparaison, ou il devrait être assez intelligent pour se comporter exactement de la même comme il l'aurait fait il a une fonction lambda de type et pas de std::function (je sais, dans ce cas, il ne peut pas être un type lambda, mais vous savez, je demande en général) ?

(J'utilise GCC, mais j'aimerais savoir ce qu'populaire compilateurs n'en général)

RÉSUMÉ, APRÈS J'AI EU BEAUCOUP DE RÉPONSES:

Si la vitesse est critique, la meilleure solution est d'utiliser une classe avec l'opérateur() aka foncteur. C'est easiesr pour le compilateur d'optimiser et d'éviter toute indirections.

Pour un entretien facilité et une meilleure solution généraliste, à l'aide de C++11 fonctionnalités, de l'utilisation de std::function. C'est toujours rapide (juste un peu plus lent que le foncteur, mais il peut être négligeable) et vous pouvez utiliser la fonction std::function, lambda, tout objet appelable.

Il y a aussi une option pour utiliser un pointeur de fonction, mais si il n'y a pas de problème de vitesse je pense que std::function est mieux (si vous utilisez le C++11).

Il y a une option pour définir la fonction lambda somewher d'autre, mais alors, vous ne gagnez rien à partir de la fonction de comparaison étant une expression lambda, puisque vous pouvez tout aussi bien en faire une classe avec l'opérateur() et l'emplacement de la définition ne serait pas l'ensemble de la construction, de toute façon.

Il y a plus d'idées, comme l'utilisation de la délégation. Si vous voulez une explication plus complète de toutes les solutions, lire les réponses :)

35voto

metal Points 1513

Il n'est pas improbable que le compilateur ne puisse pas intégrer un appel std :: function, alors que tout compilateur prenant en charge lambdas inclurait presque certainement la version de functor, y compris si ce dernier est un lambda non masqué par std :: function.

Vous pouvez utiliser decltype pour obtenir le type de comparateur de lambda:

 #include <set>
#include <iostream>
#include <iterator>
#include <algorithm>

int main()
{
   auto comp = [](int x, int y){ return x < y; };
   std::set<int,decltype(comp)> s(comp);

   s.insert(1);
   s.insert(10);
   s.insert(1);
   s.insert(2);

   std::copy( s.begin(), s.end(), std::ostream_iterator<int>(std::cout));
}
 

28voto

Yakk Points 31636

Oui, un std::function introduit, presque inévitables indirection pour votre set. Alors que le compilateur peut toujours, en théorie, comprendre que toute utilisation de votre sets' std::function implique l'appel sur un lambda qui est toujours le même lambda, qui est à la fois dur et extrêmement fragiles.

Fragile, parce que, avant le compilateur peut prouver à lui-même que tous les appels pour qui std::function sont en fait des appels à votre lambda, il doit prouver qu'aucun accès à votre std::set jamais définit l' std::function de rien, mais votre lambda. Ce qui signifie qu'il a à traquer toutes les voies possibles pour atteindre votre std::set dans toutes les unités de compilation et de prouver aucun d'entre eux de le faire.

Cela pourrait être possible, dans certains cas, mais relativement inoffensif changements pourraient casser même si votre compilateur réussi à le prouver.

D'autre part, un foncteur avec un apatride operator() est facile de prouver que le comportement et les optimisations impliquant que sont les choses de tous les jours.

Donc, oui, dans la pratique, je serais suspect std::function peut être plus lente. D'autre part, std::function solution est plus facile à maintenir que l' make_set l'un et l'échange de ressources de programmation pour la performance du programme est assez fongibles.

make_set a le grave inconvénient que ces sets'type doit être déduit à partir de l'appel à make_set. Souvent un set magasins persistante de l'état, et non pas quelque chose que vous créez sur la pile puis de laisser tomber hors de portée.

Si vous avez créé un globales ou statiques apatrides lambda auto MyComp = [](A const&, A const&)->bool { ... }, vous pouvez utiliser l' std::set<A, decltype(MyComp)> de la syntaxe pour créer un set qui peut persister, mais est facile pour le compilateur d'optimiser (parce que toutes les instances de l' decltype(MyComp) sont apatrides foncteurs) et en ligne. Je souligne cela parce que vous vous en tenez l' set en struct. (Ou est-ce que votre prise en charge du compilateur

struct Foo {
  auto mySet = make_set<int>([](int l, int r){ return l<r; });
};

ce qui est je trouve surprenant!)

Enfin, si vous êtes inquiet au sujet de la performance, de considérer qu' std::unordered_set est beaucoup plus rapide (au prix d'être incapable de parcourir le contenu dans l'ordre, et d'avoir à écrire/trouver un bon hachage), et qu'une triés std::vector est mieux si vous avez un 2-phase "insérer tout" puis "contenu de la requête à plusieurs reprises". Simplement de le ranger dans l' vector d'abord, puis sort unique erase, puis utilisez le libre - equal_range de l'algorithme.

7voto

Jonathan Wakely Points 45593

Un lambda sans état (c'est-à-dire sans capture) peut se décomposer en un pointeur de fonction, votre type pourrait donc être:

 std::set<int, bool (*)(int, int)> numbers;
 

Sinon, je choisirais la solution make_set . Si vous n'utilisez pas une fonction de création d'une ligne parce que ce n'est pas standard, vous n'aurez pas beaucoup de code à écrire!

1voto

user1095108 Points 3249

D'après mon expérience avec le profileur, le meilleur compromis entre performance et beauté consiste à utiliser une implémentation de délégué personnalisée, telle que:

http://codereview.stackexchange.com/questions/14730/impossibly-fast-delegate-in-c11

Comme std::function est généralement un peu trop lourd. Je ne peux pas commenter sur votre situation particulière, car je ne les connais pas, cependant.

1voto

ecatmur Points 64173

Si vous êtes déterminé à avoir le set en tant que membre de la classe, de l'initialisation d'un élément de référence au moment du constructeur, alors au moins un niveau d'indirection est inévitable. Considérer que dans la mesure où le compilateur sait, vous pourriez ajouter un autre constructeur:

 Foo () : numbers ([](int x, int y)
                   {
                       return x < y;
                   })
 {
 }

 Foo (char) : numbers ([](int x, int y)
                   {
                       return x > y;
                   })
 {
 }

Une fois que vous avez un objet de type Foo, le type de l' set ne portent pas d'information sur laquelle le constructeur initialisé son comparateur, afin d'appeler la bonne lambda nécessite une indirection supplémentaire au moment de l'exécution sélectionné lambda operator().

Depuis que vous utilisez captureless lambdas, vous pouvez utiliser la fonction de type de pointeur bool (*)(int, int) que votre comparateur de type, comme captureless lambdas ont la fonction de conversion approprié. Ce serait bien impliquer une indirection via le pointeur de fonction.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X