Perché std :: queue :: pop non restituisce il valore.?


123

Ho esaminato questa pagina ma non sono in grado di ottenere il motivo per lo stesso. Là è detto che

"è più sensato che non restituisca alcun valore e richieda ai client di usare front () per ispezionare il valore all'inizio della coda"

Ma l'ispezione di un elemento da front () richiedeva anche che quell'elemento fosse copiato in lvalue. Ad esempio in questo segmento di codice

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

/ * qui verrà creato temporaneo su RHS che sarà assegnato al risultato, e nel caso in cui se ritorni per riferimento, il risultato sarà reso non valido dopo l'operazione pop * /

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

sulla quinta riga, l' oggetto cout crea prima una copia di myqueue.front (), quindi la assegna al risultato. Allora, qual è la differenza, la funzione pop avrebbe potuto fare la stessa cosa.


Perché è implementato in questo modo (cioè void std::queue::pop();).
101010

La domanda ha avuto risposta, ma come nota a margine: se vuoi davvero un pop che ritorni, può essere facilmente implementato con una funzione gratuita: ideone.com/lnUzf6
eerorika

1
Il tuo collegamento è alla documentazione STL. Ma stai chiedendo della libreria standard C ++. Cose differenti.
juanchopanza

5
"Ma l'ispezione di un elemento da front()richiedeva anche la copia di quell'elemento in lvalue" - no, non lo è. frontrestituisce un riferimento, non un valore. È possibile controllare il valore a cui si riferisce senza copiarlo.
Mike Seymour

1
@KeminZhou il modello che descrivi richiede una copia. Può essere. Se si desidera multiplexare il consumo della coda, sì, è necessario crearne una copia prima di rilasciare il blocco sulla coda. Tuttavia, se ti interessa solo separare input e output, non è necessario un lucchetto per ispezionare la parte anteriore. Potresti aspettare di bloccare fino a quando non hai finito di consumarlo e devi chiamare pop(). Se si utilizza, std::queue<T, std::list<T>>non vi è alcuna preoccupazione che il riferimento fornito front()venga invalidato da un file push(). Ma devi conoscere il tuo modello di utilizzo e documentare i tuoi vincoli.
jwm

Risposte:


105

Allora, qual è la differenza, la funzione pop avrebbe potuto fare la stessa cosa.

Avrebbe potuto effettivamente fare la stessa cosa. Il motivo per cui non l'ha fatto è perché un pop che ha restituito l'elemento estratto non è sicuro in presenza di eccezioni (dovendo restituire per valore e quindi creare una copia).

Considera questo scenario (con un'implementazione pop ingenua / inventata, per illustrare il mio 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
    }

Se il costruttore di copia di T lancia al ritorno, hai già modificato lo stato della coda ( top_positionnella mia implementazione ingenua) e l'elemento viene rimosso dalla coda (e non restituito). A tutti gli effetti (indipendentemente da come si cattura l'eccezione nel codice client) l'elemento in cima alla coda viene perso.

Questa implementazione è anche inefficiente nel caso in cui non sia necessario il valore estratto (ovvero crea una copia dell'elemento che nessuno utilizzerà).

Questo può essere implementato in modo sicuro ed efficiente, con due operazioni separate ( void pope const T& front()).


hmmm ... ha senso
cbinder

37
Nota C ++ 11: se T ha un costruttore di mosse noexcept economico (che è spesso il caso del tipo di oggetti messi in stack), la restituzione per valore è efficiente e sicura rispetto alle eccezioni.
Roman L

9
@ DavidRodríguez-dribeas: Non sto suggerendo alcun cambiamento, il mio punto era che alcuni degli svantaggi menzionati diventano meno un problema con C ++ 11.
Roman L

11
Ma perché si chiama pop? è abbastanza controintuitivo. Potrebbe essere chiamato drop, e quindi è chiaro a tutti che non fa
apparire

12
@utnapistim: l'operazione "pop" di fatto è sempre stata quella di prendere l'elemento in alto da uno stack e restituirlo. Quando mi sono imbattuto per la prima volta in stack STL sono rimasto sorpreso, per non dire altro, di popnon aver restituito nulla. Vedi ad esempio su wikipedia .
Cris Luengo

34

La pagina a cui ti sei collegato risponde alla tua domanda.

Per citare l'intera sezione rilevante:

Ci si potrebbe chiedere perché pop () restituisce void, invece di value_type. Cioè, perché si deve usare front () e pop () per esaminare e rimuovere l'elemento all'inizio della coda, invece di combinare i due in una singola funzione membro? In effetti, c'è una buona ragione per questo design. Se pop () restituisse l'elemento in primo piano, dovrebbe restituire per valore invece che per riferimento: il ritorno per riferimento creerebbe un puntatore penzolante. La restituzione per valore, tuttavia, è inefficiente: implica almeno una chiamata ridondante al costruttore di copia. Poiché è impossibile per pop () restituire un valore in modo tale da essere sia efficiente che corretto, è più sensato che non restituisca alcun valore e richieda ai client di utilizzare front () per ispezionare il valore in la parte anteriore della coda.

Il C ++ è progettato pensando all'efficienza, considerando il numero di righe di codice che il programmatore deve scrivere.


16
Forse, ma la vera ragione è che è impossibile implementare una versione sicura delle eccezioni (con la forte garanzia) per una versione di popcui restituisce un valore.
James Kanze

5

pop non può restituire un riferimento al valore rimosso, poiché viene rimosso dalla struttura dati, quindi a cosa deve fare riferimento il riferimento? Potrebbe restituire per valore, ma cosa succede se il risultato del pop non viene memorizzato da nessuna parte? Quindi si perde tempo a copiare il valore inutilmente.


4
La vera ragione è la sicurezza eccezionale. Non esiste un modo sicuro per avere una "transazione" con lo stack (o l'elemento rimane nello stack o ti viene restituito) se popritorni e l'atto di restituzione può risultare in un'eccezione. Ovviamente dovrebbe rimuovere l'elemento prima di restituirlo, e quindi se qualcosa lancia l'elemento potrebbe essere perso irrevocabilmente.
Jon

1
Questa preoccupazione si applica ancora con un costruttore di mosse noexcept? (Ovviamente 0 copie sono più efficienti di due mosse, ma questo potrebbe aprire la porta a un'eccezione sicura, efficace combinato fronte + pop)
peppe

1
@peppe, puoi restituire per valore se sai che value_typeha un costruttore di movimento nothrow, ma l'interfaccia della coda sarebbe diversa a seconda del tipo di oggetto che ci si memorizza, il che non sarebbe utile.
Jonathan Wakely

1
@peppe: Sarebbe sicuro, ma lo stack è una classe di libreria generica e quindi più deve supporre sul tipo di elemento meno è utile.
Jon

So che l'STL non lo consentirebbe, ma ciò significa che si può costruire la propria classe di coda con il blackjack e ^ W ^ W ^ W con questa funzione :)
peppe

3

Con l'attuale implementazione, questo è valido:

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

Se pop restituisse un riferimento, in questo modo:

value_type& pop();

Quindi il seguente codice potrebbe bloccarsi, poiché il riferimento non è più valido:

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

D'altra parte, se restituisse direttamente un valore:

value_type pop();

Quindi dovresti fare una copia affinché questo codice funzioni, che è meno efficiente:

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

1

A partire da C ++ 11 sarebbe possibile archiviare il comportamento desiderato utilizzando la semantica di spostamento. Mi piace pop_and_move. Quindi il costruttore di copia non verrà chiamato e le prestazioni dipenderanno solo dal costruttore di spostamento.


2
No, la semantica dello spostamento non rende magicamente popsicura l'eccezione.
LF

0

Puoi farlo totalmente:

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

Oppure, se vuoi il valore in una variabile, usa un riferimento:

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

Accanto a questo: la formulazione "più sensato" è una forma soggettiva di "abbiamo esaminato i modelli di utilizzo e abbiamo riscontrato più bisogno di una divisione". (Stai tranquillo: il linguaggio C ++ non si sta evolvendo leggermente ...)


0

Penso che la soluzione migliore sarebbe aggiungere qualcosa di simile

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

dove value riceverà il valore scoppiato.

Il vantaggio è che potrebbe essere implementato utilizzando un operatore di assegnazione di spostamento, mentre l'uso di front + pop farà una copia.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.