16 votes

Pourquoi les prédicats des algorithmes d'opérations de séquence sont-ils passés par copie?

Je me demande pourquoi les foncteurs sont passés par copie aux fonctions des algorithm:

template  struct summatory
{
    summatory() : result(T()) {}

    void operator()(const T& value)
    { result += value; std::cout << value << "; ";};

    T result;
};

std::array a {{ 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }};
summatory sum;

std::cout << "\nLa somme de : ";
std::for_each(a.begin(), a.end(), sum);
std::cout << "est : " << sum.result;

Je m'attendais à la sortie suivante:

La somme de : 1; 1; 2; 3; 5; 8; 13; 21; 34; 55; est : 143

Mais sum.result contient 0, qui est la valeur par défaut assignée dans le constructeur. La seule façon d'obtenir le comportement souhaité est de capturer la valeur de retour du for_each:

sum = std::for_each(a.begin(), a.end(), sum);
std::cout << "est : " << sum.result;

Cela se produit car le foncteur est passé par copie au for_each au lieu de par référence:

template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );

Le foncteur externe reste donc inchangé, tandis que celui interne (qui est une copie du foncteur externe) est mis à jour et est retourné après l'exécution de l'algorithme (démonstration en direct), donc le résultat est copié (ou déplacé) encore après avoir effectué toutes les opérations.


Il doit y avoir une bonne raison de faire le travail de cette manière, mais je ne comprends pas vraiment la logique de cette conception, donc mes questions sont les suivantes:

  • Pourquoi les prédicats des algorithmes d'opérations de séquence sont-ils passés par copie au lieu de par référence?
  • Quels avantages offre l'approche par copie par rapport à l'approche par référence?

7voto

Balog Pal Points 6584

C'est principalement pour des raisons historiques. En '98, lorsque tout l'algo est entré dans les références standard, il y avait toutes sortes de problèmes. Cela a finalement été résolu par le biais de DRs cores et de bibliothèques à partir de C++03 et au-delà. De plus, les « ref-wrappers » sensibles et le « bind » fonctionnel n'arrivèrent que dans TR1.

Ceux qui ont essayé d'utiliser des algos avec le début de C++98, en utilisant des fonctions avec des paramètres de référence ou des retours, peuvent se rappeler de toutes sortes de problèmes. Les algos auto-écrits étaient également sujets à rencontrer le redoutable problème de "référence à une référence".

Passer par valeur fonctionnait au moins correctement et ne créait guère de problèmes -- et Boost avait des ref et cref dès le début pour vous aider là où vous aviez besoin de ajuster.

5voto

PlasmaHH Points 8426

Ceci n'est qu'une supposition, mais...

... supposons un instant qu'il prenne par référence constante. Cela signifierait que tous vos membres doivent être modifiables et que l'opérateur doit être const. Ça ne semble tout simplement pas "correct".

... supposons un instant qu'il prenne par référence non-constante. Cela appellerait un opérateur non-const, les membres peuvent juste être manipulés sans problème. Mais que se passe-t-il si vous voulez passer un objet ad-hoc? Comme le résultat d'une opération de liaison (même C++98 avait -- moches et simples -- des outils de liaison)? Ou si le type lui-même fait tout ce dont vous avez besoin et que vous n'avez plus besoin de l'objet après cela et que vous voulez simplement l'appeler comme for_each(b,e,my_functor());? Cela ne fonctionnera pas car les temporaires ne peuvent pas être liés à des références non-constantes.

Alors peut-être pas la meilleure option, mais la moins mauvaise est de prendre par valeur, de le copier autant de fois que nécessaire dans le processus (espérons que ce ne soit pas trop fréquent) et puis, une fois terminé, le retourner de for_each. Cela fonctionne bien avec la complexité relativement faible de votre objet sommatoire, n'a pas besoin de choses modifiables ajoutées comme l'approche par référence constante, et fonctionne également avec les temporaires.

Mais cela dépendra de votre situation, tout comme cela a probablement été pour les membres du comité, et je suppose que c'était finalement un vote sur ce qu'ils pensaient être le plus susceptible de convenir au plus grand nombre de cas d'utilisation.

1voto

Enigma Points 1394

Peut-être que cela pourrait être une solution de contournement. Capturez le foncteur par référence et appelez-le dans une lambda

std::for_each(a.begin(), a.end(), [&sum] (T& value) 
    {
        sum(value);   
    });

std::cout << "is: " << sum.result;

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