Pourquoi std :: queue :: pop ne renvoie pas de valeur.?

123

J'ai parcouru cette page mais je ne suis pas en mesure d'en obtenir la même raison. Là, il est mentionné que

"il est plus judicieux pour lui de ne renvoyer aucune valeur et de demander aux clients d'utiliser front () pour inspecter la valeur au début de la file d'attente"

Mais l'inspection d'un élément depuis front () nécessitait également que cet élément soit copié dans lvalue. Par exemple dans ce segment de code

std::queue<int> myqueue;
int myint;
int result;
std::cin >> myint;
myqueue.push (myint);

/ * ici temporaire sera créé sur RHS qui sera affecté au résultat, et dans le cas où si retourne par référence, le résultat sera rendu invalide après l'opération pop * /

result = myqueue.front();  //result.
std::cout << ' ' << result;
myqueue.pop();

sur la cinquième ligne, l' objet cout crée d'abord une copie de myqueue.front () puis l'affecte au résultat. Alors, quelle est la différence, la fonction pop aurait pu faire la même chose.

cbinder
la source
Parce que est mis en œuvre de cette façon (c'est-à-dire void std::queue::pop();).
101010
La question a été répondue, mais en guise de remarque: si vous voulez vraiment une pop qui revient, elle peut être facilement implémentée avec une fonction gratuite: ideone.com/lnUzf6
eerorika
1
Votre lien est vers la documentation STL. Mais vous posez des questions sur la bibliothèque standard C ++. Différentes choses.
juanchopanza
5
"Mais inspecter un élément depuis front()exigeait également que cet élément soit copié dans lvalue" - non, ce n'est pas le cas. frontrenvoie une référence, pas une valeur. Vous pouvez inspecter la valeur à laquelle elle fait référence sans la copier.
Mike Seymour
1
@KeminZhou le modèle que vous décrivez nécessite une copie. Peut être. Si vous voulez multiplexer la consommation de la file d'attente, alors oui, vous devez faire une copie avant de libérer le verrou sur la file d'attente. Cependant, si vous ne vous souciez que de séparer l'entrée et la sortie, vous n'avez pas besoin d' un verrou pour inspecter l'avant. Vous pouvez attendre pour verrouiller jusqu'à ce que vous ayez fini de le consommer et que vous deviez appeler pop(). Si vous utilisez, std::queue<T, std::list<T>>il n'y a aucun problème à ce que la référence fournie front()soit invalidée par un push(). Mais vous devez connaître votre modèle d'utilisation et documenter vos contraintes.
jwm le

Réponses:

105

Alors, quelle est la différence, la fonction pop aurait pu faire la même chose.

Il aurait en effet pu faire la même chose. La raison pour laquelle ce n'est pas le cas, est qu'un pop qui a renvoyé l'élément popped est dangereux en présence d'exceptions (devoir retourner par valeur et donc créer une copie).

Considérez ce scénario (avec une implémentation pop naïve / inventée, pour illustrer mon propos):

template<class T>
class queue {
    T* elements;
    std::size_t top_position;
    // stuff here
    T pop()
    {
        auto x = elements[top_position];
        // TODO: call destructor for elements[top_position] here
        --top_position;  // alter queue state here
        return x;        // calls T(const T&) which may throw
    }

Si le constructeur de copie de T lance au retour, vous avez déjà modifié l'état de la file d'attente ( top_positiondans mon implémentation naïve) et l'élément est supprimé de la file d'attente (et non retourné). À toutes fins utiles (quelle que soit la manière dont vous interceptez l'exception dans le code client), l'élément en haut de la file d'attente est perdu.

Cette implémentation est également inefficace dans le cas où vous n'avez pas besoin de la valeur sautée (c'est-à-dire qu'elle crée une copie de l'élément que personne n'utilisera).

Cela peut être mis en œuvre de manière sûre et efficace, avec deux opérations distinctes ( void popet const T& front()).

utnapistim
la source
hmmm ... that makes sense
cbinder
37
Remarque C ++ 11: si T a un constructeur de mouvement noexcept bon marché (ce qui est souvent le cas pour le type d'objets mis en pile), alors le retour par valeur est efficace et sans exception.
Roman L
9
@ DavidRodríguez-dribeas: Je ne suggère aucun changement, mon point était que certains des inconvénients mentionnés deviennent moins problématiques avec C ++ 11.
Roman L
11
Mais pourquoi est-il appelé pop? c'est assez contre-intuitif. Il pourrait être nommé drop, et alors il est clair pour tout le monde qu'il ne fait pas apparaître l'élément, mais le laisse tomber à la place ...
UeliDeSchwert
12
@utnapistim: l'opération "pop" de facto a toujours été de prendre l'élément supérieur d'une pile et de le renvoyer. Quand je suis tombé sur des piles STL pour la première fois, j'ai été surpris, c'est le moins qu'on puisse dire, de popne rien rendre. Voir par exemple sur wikipedia .
Cris Luengo
34

La page à laquelle vous avez lié répond à votre question.

Pour citer toute la section pertinente:

On pourrait se demander pourquoi pop () renvoie void, au lieu de value_type. Autrement dit, pourquoi doit-on utiliser front () et pop () pour examiner et supprimer l'élément au début de la file d'attente, au lieu de combiner les deux dans une seule fonction membre? En fait, il y a une bonne raison à cette conception. Si pop () retournait l'élément avant, il devrait retourner par valeur plutôt que par référence: return by reference créerait un pointeur suspendu. Le retour par valeur, cependant, est inefficace: il implique au moins un appel de constructeur de copie redondant. Puisqu'il est impossible pour pop () de renvoyer une valeur de manière à être à la fois efficace et correcte, il est plus judicieux pour elle de ne renvoyer aucune valeur du tout et d'exiger que les clients utilisent front () pour inspecter la valeur à le devant de la file d'attente.

C ++ est conçu dans un souci d'efficacité, sur le nombre de lignes de code que le programmeur doit écrire.

BPF2010
la source
16
Peut-être, mais la vraie raison est qu'il est impossible d'implémenter une version sans exception (avec la garantie forte) pour une version popdont renvoie une valeur.
James Kanze
5

pop ne peut pas renvoyer une référence à la valeur qui est supprimée, car elle est supprimée de la structure de données, alors à quoi la référence doit-elle faire référence? Il pourrait renvoyer par valeur, mais que faire si le résultat de pop n'est stocké nulle part? Ensuite, du temps est perdu à copier la valeur inutilement.

Neil Kirk
la source
4
La vraie raison est la sécurité d'exception. Il n'y a pas de moyen sûr d'avoir une "transaction" avec la pile (soit l'élément reste sur la pile, soit il vous est retourné) si les popretours et l'acte de retour peuvent entraîner une exception. Il devrait évidemment supprimer l'élément avant de le renvoyer, et si quelque chose le lance, l'élément pourrait être irrévocablement perdu.
Jon
1
Cette préoccupation s'applique-t-elle toujours avec fi a noexcept move constructeur? (Bien sûr, 0 copie est plus efficace que deux coups, mais cela pourrait ouvrir la porte à une combinaison frontale + pop exceptionnellement sûre et efficace)
peppe
1
@peppe, vous pouvez retourner par valeur si vous savez que le value_typea un constructeur de mouvement nothrow, mais l'interface de la file d'attente serait alors différente selon le type d'objet que vous y stockez, ce qui ne serait pas utile.
Jonathan Wakely
1
@peppe: Ce serait sûr, mais stack est une classe de bibliothèque générique et donc plus il doit en supposer sur le type d'élément, moins il est utile.
Jon
Je sais que la STL ne permettrait pas cela, mais cela signifie que l'on peut créer sa propre classe de file d'attente avec le blackjack et ^ W ^ W ^ W avec cette fonctionnalité :)
peppe
3

Avec l'implémentation actuelle, ceci est valide:

int &result = myqueue.front();
std::cout << result;
myqueue.pop();

Si pop renverrait une référence, comme ceci:

value_type& pop();

Ensuite, le code suivant pourrait planter, car la référence n'est plus valide:

int &result = myqueue.pop();
std::cout << result;

En revanche, s'il renverrait une valeur directement:

value_type pop();

Ensuite, vous devrez faire une copie pour que ce code fonctionne, ce qui est moins efficace:

int result = myqueue.pop();
std::cout << result;
Jan Rüegg
la source
1

À partir de C ++ 11, il serait possible d'archiver le comportement souhaité en utilisant la sémantique de déplacement. Comme pop_and_move. Le constructeur de copie ne sera donc pas appelé et les performances dépendront uniquement du constructeur de déplacement.

Vyacheslav Putsenko
la source
2
Non, la sémantique de déplacement ne rend pas les popexceptions sûres comme par magie .
LF
0

Vous pouvez totalement faire ceci:

std::cout << ' ' << myqueue.front();

Ou, si vous voulez la valeur dans une variable, utilisez une référence:

const auto &result = myqueue.front();
if (result > whatever) do_whatever();
std::cout << ' ' << result;

À côté de cela: le libellé «plus sensible» est une forme subjective de «nous avons examiné les modèles d'utilisation et avons constaté plus de besoin d'une scission». (Rassurez-vous: le langage C ++ n'évolue pas à la légère ...)

xtofl
la source
0

Je pense que la meilleure solution serait d'ajouter quelque chose comme

std::queue::pop_and_store(value_type& value);

où valeur recevra la valeur sautée.

L'avantage est qu'il pourrait être implémenté en utilisant un opérateur d'assignation de déplacement, tandis que l'utilisation de front + pop fera une copie.

Masse Nicolas
la source