¿Por qué std :: queue :: pop no devuelve el valor?

123

Revisé esta página pero no puedo obtener el motivo de la misma. Allí se menciona que

"Es más sensato que no devuelva ningún valor y que requiera que los clientes usen front () para inspeccionar el valor al principio de la cola"

Pero inspeccionar un elemento desde front () también requería que ese elemento se copiara en lvalue. Por ejemplo, en este segmento de código

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

/ * aquí se creará temporal en RHS que se asignará al resultado, y en caso de que se devuelva por referencia, el resultado se invalidará después de la operación pop * /

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

en la quinta línea, el objeto cout primero crea una copia de myqueue.front () y luego lo asigna al resultado. Entonces, cuál es la diferencia, la función pop podría haber hecho lo mismo.

escoria
fuente
Porque se implementa de esta manera (es decir, void std::queue::pop();).
101010
La pregunta ha sido respondida, pero como nota al margen: si realmente quieres un pop que regrese, se puede implementar fácilmente con una función gratuita: ideone.com/lnUzf6
eerorika
1
Su enlace es a la documentación STL. Pero estás preguntando por la biblioteca estándar de C ++. Cosas diferentes.
juanchopanza
5
"Pero inspeccionar un elemento de front()también requería que ese elemento se copiara en lvalue", no, no es así. frontdevuelve una referencia, no un valor. Puede inspeccionar el valor al que se refiere sin copiarlo.
Mike Seymour
1
@KeminZhou, el modelo que describe requiere una copia. Tal vez. Si desea multiplexar el consumo de la cola, entonces sí, debe hacer una copia antes de liberar el bloqueo de la cola. Sin embargo, si solo le importa separar la entrada y la salida, entonces no necesita un candado para inspeccionar el frente. Puede esperar para bloquear hasta que haya terminado de consumirlo y necesite llamar pop(). Si lo usa std::queue<T, std::list<T>>, no hay ninguna preocupación acerca de que la referencia proporcionada front()sea ​​invalidada por un push(). Pero debes conocer tu patrón de uso y documentar tus limitaciones.
jwm

Respuestas:

105

Entonces, cuál es la diferencia, la función pop podría haber hecho lo mismo.

De hecho, podría haber hecho lo mismo. La razón por la que no lo hizo es porque una ventana emergente que devolvió el elemento emergente no es segura en presencia de excepciones (tener que regresar por valor y, por lo tanto, crear una copia).

Considere este escenario (con una implementación pop ingenua / inventada, para ilustrar mi punto):

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 el constructor de copia de T arroja un retorno, ya ha alterado el estado de la cola ( top_positionen mi implementación ingenua) y el elemento se elimina de la cola (y no se devuelve). Para todos los efectos (sin importar cómo detecte la excepción en el código del cliente), el elemento en la parte superior de la cola se pierde.

Esta implementación también es ineficaz en el caso de que no necesite el valor emergente (es decir, crea una copia del elemento que nadie usará).

Esto se puede implementar de manera segura y eficiente, con dos operaciones separadas ( void popy const T& front()).

utnapistim
fuente
hmmm ... eso tiene sentido
cbinder
37
Nota de C ++ 11: si T tiene un constructor de movimiento sin excepción barato (que suele ser el caso para el tipo de objetos colocados en pilas), devolver por valor es eficiente y seguro para excepciones.
Roman L
9
@ DavidRodríguez-dribeas: No estoy sugiriendo ningún cambio, mi punto fue que algunos de los inconvenientes mencionados se vuelven menos problemáticos con C ++ 11.
Roman L
11
Pero, ¿por qué se llama pop? eso es bastante contrario a la intuición. Podría ser nombrado drop, y luego está claro para todos que no hace estallar el elemento, sino que lo deja caer ...
UeliDeSchwert
12
@utnapistim: la operación "pop" de facto siempre ha sido tomar el elemento superior de una pila y devolverlo. Cuando me encontré por primera vez con pilas de STL, me sorprendió, por decir lo menos, popno devolver nada. Consulte, por ejemplo, en wikipedia .
Cris Luengo
34

La página a la que ha vinculado responde a su pregunta.

Para citar toda la sección relevante:

Uno podría preguntarse por qué pop () devuelve void, en lugar de value_type. Es decir, ¿por qué se debe usar front () y pop () para examinar y eliminar el elemento al principio de la cola, en lugar de combinar los dos en una función de un solo miembro? De hecho, hay una buena razón para este diseño. Si pop () devolvió el elemento frontal, tendría que regresar por valor en lugar de por referencia: regresar por referencia crearía un puntero colgante. Sin embargo, el retorno por valor es ineficaz: implica al menos una llamada al constructor de copia redundante. Dado que es imposible que pop () devuelva un valor de tal manera que sea eficiente y correcto, es más sensato que no devuelva ningún valor y que requiera que los clientes usen front () para inspeccionar el valor en al frente de la cola.

C ++ está diseñado teniendo en cuenta la eficiencia, sobre la cantidad de líneas de código que el programador tiene que escribir.

BPF2010
fuente
16
Quizás, pero la verdadera razón es que es imposible implementar una versión segura de excepción (con la fuerte garantía) para una versión de la popcual devuelve un valor.
James Kanze
5

pop no puede devolver una referencia al valor que se elimina, ya que se elimina de la estructura de datos, entonces, ¿a qué debería referirse la referencia? Podría devolver por valor, pero ¿qué pasa si el resultado de pop no se almacena en ningún lugar? Entonces se pierde tiempo copiando el valor innecesariamente.

Neil Kirk
fuente
4
La verdadera razón es la seguridad excepcional. No hay una forma segura de tener una "transacción" con la pila (o el elemento permanece en la pila o se le devuelve) si las popdevoluciones y el acto de regresar pueden resultar en una excepción. Obviamente, tendría que eliminar el elemento antes de devolverlo, y luego, si algo arroja, el elemento podría perderse irrevocablemente.
Jon
1
¿Esta preocupación todavía se aplica con fi a noexcept move constructor? (Por supuesto, 0 copias son más eficientes que dos movimientos, pero eso podría abrir la puerta para una excepción, segura y eficiente frente + pop combinado)
peppe
1
@peppe, puede devolver por valor si sabe que value_typetiene un constructor de movimiento nothrow, pero la interfaz de la cola sería diferente dependiendo del tipo de objeto que almacene en ella, lo que no sería útil.
Jonathan Wakely
1
@peppe: Eso sería seguro, pero la pila es una clase de biblioteca genérica y, por lo tanto, cuanto más tiene que asumir sobre el tipo de elemento, menos útil es.
Jon
Sé que STL no lo permitiría, pero eso significa que uno puede crear su propia clase de cola con blackjack y ^ W ^ W ^ W con esta función :)
peppe
3

Con la implementación actual, esto es válido:

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

Si pop devuelve una referencia, como esta:

value_type& pop();

Entonces el siguiente código podría fallar, ya que la referencia ya no es válida:

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

Por otro lado, si devuelve un valor directamente:

value_type pop();

Entonces necesitaría hacer una copia para que este código funcione, lo cual es menos eficiente:

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

A partir de C ++ 11 sería posible archivar el comportamiento deseado utilizando la semántica de movimiento. Al igual pop_and_move. Por lo tanto, no se llamará al constructor de copia y el rendimiento dependerá únicamente del constructor de movimiento.

Vyacheslav Putsenko
fuente
2
No, la semántica de movimientos no hace que las popexcepciones sean mágicamente seguras.
LF
0

Puedes hacer esto totalmente:

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

O, si desea el valor en una variable, use una referencia:

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

Además de eso: la redacción 'más sensible' es una forma subjetiva de 'buscamos en los patrones de uso y encontramos más necesidad de una división'. (Tenga la seguridad: el lenguaje C ++ no está evolucionando a la ligera ...)

xtofl
fuente
0

Creo que la mejor solución sería agregar algo como

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

donde value recibirá el valor reventado.

La ventaja es que podría implementarse usando un operador de asignación de movimiento, mientras que usar front + pop hará una copia.

Masse Nicolas
fuente