web-dev-qa-db-fra.com

Pourquoi remplacer l'opérateur ()?

Dans la bibliothèque Boost Signals , ils surchargent l'opérateur ().

Est-ce une convention en C++? Pour les rappels, etc.?

J'ai vu cela dans le code d'un collègue (qui se trouve être un grand fan de Boost). De toute la bonté Boost là-bas, cela n'a conduit qu'à la confusion pour moi.

Avez-vous une idée de la raison de cette surcharge?

49
JeffV

L'un des principaux objectifs lors de la surcharge de l'opérateur () est de créer un foncteur. Un foncteur agit comme une fonction, mais il a l'avantage d'être avec état, ce qui signifie qu'il peut conserver les données reflétant son état entre les appels.

Voici un exemple simple de foncteur:

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"

Les foncteurs sont fortement utilisés avec la programmation générique. De nombreux algorithmes STL sont écrits de manière très générale, de sorte que vous pouvez brancher votre propre fonction/foncteur dans l'algorithme. Par exemple, l'algorithme std :: for_each vous permet d'appliquer une opération sur chaque élément d'une plage. Il pourrait être mis en œuvre quelque chose comme ça:

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

Vous voyez que cet algorithme est très générique car il est paramétré par une fonction. En utilisant l'opérateur (), cette fonction vous permet d'utiliser soit un foncteur soit un pointeur de fonction. Voici un exemple montrant les deux possibilités:

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

Concernant votre question sur la surcharge de l'opérateur (), eh oui c'est possible. Vous pouvez parfaitement écrire un foncteur qui a plusieurs parenthèses opérateur, tant que vous respectez les règles de base de la surcharge de méthode (par exemple, la surcharge uniquement sur le type de retour n'est pas possible).

133
Luc Touraille

Il permet à une classe d'agir comme une fonction. Je l'ai utilisé dans une classe de journalisation où l'appel devrait être une fonction mais je voulais l'avantage supplémentaire de la classe.

donc quelque chose comme ça:

logger.log("Log this message");

se transforme en ceci:

logger("Log this message");
24
Lodle

Beaucoup ont répondu qu'il fait un foncteur, sans dire une grande raison pour laquelle un foncteur est meilleur qu'une ancienne fonction ordinaire.

La réponse est qu'un foncteur peut avoir un état. Envisagez une fonction de sommation - elle doit conserver un total cumulé.

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

Un foncteur n'est pas une fonction, vous ne pouvez donc pas le surcharger.
Votre collègue a raison, cependant, que la surcharge de operator () est utilisée pour créer des "foncteurs" - des objets qui peuvent être appelés comme des fonctions. En combinaison avec des modèles qui attendent des arguments "fonctionnels", cela peut être assez puissant car la distinction entre un objet et une fonction devient floue.

Comme d'autres affiches l'ont dit: les foncteurs ont un avantage sur les fonctions simples en ce qu'ils peuvent avoir un état. Cet état peut être utilisé sur une seule itération (par exemple pour calculer la somme de tous les éléments d'un conteneur) ou sur plusieurs itérations (par exemple pour trouver tous les éléments dans plusieurs conteneurs répondant à des critères particuliers).

4
Joris Timmermans

Commencez à utiliser std::for_each, std::find_if, etc. plus souvent dans votre code et vous comprendrez pourquoi il est pratique d'avoir la possibilité de surcharger l'opérateur (). Il permet également aux foncteurs et aux tâches d'avoir une méthode d'appel claire qui n'entrera pas en conflit avec les noms d'autres méthodes dans les classes dérivées.

3
Michel

Vous pouvez également consulter le exemple Matrix de la faq C++ . Il existe de bonnes utilisations pour le faire, mais cela dépend bien sûr de ce que vous essayez d'accomplir.

2
carson

Les foncteurs sont fondamentalement comme des pointeurs de fonction. Ils sont généralement destinés à être copiables (comme les pointeurs de fonction) et invoqués de la même manière que les pointeurs de fonction. Le principal avantage est que lorsque vous disposez d'un algorithme qui fonctionne avec un foncteur basé sur un modèle, l'appel de fonction à operator () peut être intégré. Cependant, les pointeurs de fonction sont toujours des foncteurs valides.

2
Greg Rogers

L'utilisation de operator () pour former foncteurs en C++ est liée à programmation fonctionnelle paradigmes qui utilisent généralement un concept similaire: fermetures .

2
Judge Maygarden

Une force que je peux voir, mais cela peut être discuté, est que la signature de operator () ressemble et se comporte de la même manière sur différents types. Si nous avions une classe Reporter qui avait un rapport sur la méthode membre (..), puis une autre classe Writer, qui avait une méthode membre write (..), nous devions écrire des adaptateurs si nous voulions utiliser les deux classes comme peut-être un composant de modèle d'un autre système. Tout ce qui lui importerait, c'est de passer des cordes ou quoi d'autre. Sans l'utilisation de surcharge d'opérateur () ou d'écriture d'adaptateurs de type spécial, vous ne pourriez pas faire des choses comme

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

parce que T a besoin d'une fonction membre appelée write qui accepte tout ce qui peut être implicitement castable en const char * (ou plutôt const char []). La classe Reporter dans cet exemple n'a pas cela, donc avoir T (un paramètre de modèle) étant Reporter ne pourrait pas être compilé.

Cependant, pour autant que je puisse voir, cela fonctionnerait avec différents types

T t;
t("Hello world");

cependant, cela exige toujours explicitement que le type T ait un tel opérateur défini, donc nous avons toujours une exigence sur T. Personnellement, je ne pense pas que ce soit trop bizarre avec des foncteurs car ils sont couramment utilisés mais je préférerais voir d'autres mécanismes pour Ce comportement. Dans des langages comme C #, vous pouvez simplement passer un délégué. Je ne suis pas trop familier avec les pointeurs de fonction membre en C++, mais je pourrais imaginer que vous pourriez également obtenir le même comportement.

À part le comportement syntaxique du sucre, je ne vois pas vraiment les forces de la surcharge de l'opérateur pour effectuer de telles tâches.

Je suis sûr qu'il y a plus sciemment des gens qui ont de meilleures raisons que moi, mais je pensais que j'exposerais mon opinion à partager avec vous.

2
Statement

Un autre collègue a souligné que cela pourrait être un moyen de déguiser des objets foncteurs en fonctions. Par exemple, ceci:

my_functor();

Est vraiment:

my_functor.operator()();

Cela signifie-t-il ceci:

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

Peut-être également utilisé pour surcharger cela?

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

D'autres articles ont fait un bon travail décrivant comment fonctionne operator () et pourquoi il peut être utile.

J'ai récemment utilisé du code qui utilise très largement operator (). Un inconvénient de surcharger cet opérateur est que certains IDE deviennent des outils moins efficaces en conséquence. Dans Visual Studio, vous pouvez généralement cliquer avec le bouton droit sur un appel de méthode pour accéder à la définition et/ou à la déclaration de méthode. Malheureusement, VS n'est pas assez intelligent pour indexer les appels operator (). Surtout dans le code complexe avec des définitions d'opérateur () écrasées partout, il peut être très difficile de comprendre quel morceau de code exécute où. Dans plusieurs cas, j'ai trouvé que je devais exécuter le code et le parcourir pour trouver ce qui était réellement en cours d'exécution.

1
Mr Fooz