Come posso cancellare la coda std :: efficiente?


166

Sto usando std :: queue per implementare la classe JobQueue. (Fondamentalmente questa classe elabora ogni lavoro in modo FIFO). In uno scenario, desidero cancellare la coda in una sola volta (eliminare tutti i lavori dalla coda). Non vedo alcun metodo chiaro disponibile nella classe std :: queue.

Come posso implementare in modo efficiente il metodo clear per la classe JobQueue?

Ho una semplice soluzione di scoppiare in un ciclo ma sto cercando modi migliori.

//Clears the job queue
void JobQueue ::clearJobs()
 {
  // I want to avoid pop in a loop
    while (!m_Queue.empty())
    {
        m_Queue.pop();
    }
}

3
Nota dequesupporta clear
bobobobo

Risposte:


257

Un linguaggio comune per cancellare i contenitori standard è scambiare con una versione vuota del contenitore:

void clear( std::queue<int> &q )
{
   std::queue<int> empty;
   std::swap( q, empty );
}

È anche l'unico modo per cancellare effettivamente la memoria contenuta in alcuni contenitori (std :: vector)


41
Meglio ancora lo è std::queue<int>().swap(q). Con il linguaggio copia e scambia, tutto ciò dovrebbe essere equivalente a q = std::queue<int>().
Alexandre C.,

12
Sebbene std::queue<int>().swap(q)sia equivalente al codice sopra, q = std::queue<int>()non è necessario che sia equivalente. Poiché non vi è alcun trasferimento di proprietà nell'assegnazione della memoria allocata, alcuni contenitori (come il vettore) potrebbero semplicemente chiamare i distruttori degli elementi precedentemente trattenuti e impostare la dimensione (o un'operazione equivalente con i puntatori memorizzati) senza rilasciare effettivamente la memoria.
David Rodríguez - dribeas,

6
queuenon ha un swap(other)metodo, quindi queue<int>().swap(q)non compilare. Penso che devi usare il generico swap(a, b).
Dustin Boswell,

3
@ ThorbjørnLindeijer: in C ++ 03 hai ragione, in C ++ 11 le code hanno lo swap come funzione membro e inoltre c'è un sovraccarico di funzioni gratuite che scambia due code dello stesso tipo.
David Rodríguez - dribeas,

10
@ ThorbjørnLindeijer: dal punto di vista degli utenti della coda originale, questi elementi non esistono. Hai ragione nel dire che saranno distrutti uno dopo l'altro e il costo è lineare, ma non sono accessibili da nessun altro oltre alla funzione locale. In un ambiente multithread, si dovrebbe bloccare, scambiare una coda non temporanea con quella originale, sbloccare (per consentire accessi simultanei) e lasciare morire la coda scambiata. In questo modo è possibile spostare il costo della distruzione al di fuori della sezione critica.
David Rodríguez - dribeas,

46

Sì, un po 'un malfunzionamento della classe di coda, IMHO. Questo è ciò che faccio:

#include <queue>
using namespace std;;

int main() {
    queue <int> q1;
    // stuff
    q1 = queue<int>();  
}

8
@Naszta Per favore, spiega come swapè "più efficace"
bobobobo,

@bobobobo:q1.swap(queue<int>());
Naszta,

12
q1=queue<int>();è più breve e più chiaro (non ci stai davvero provando .swap, ci stai provando .clear).
bobobobo,

28
Con il nuovo C ++, q1 = {}è sufficiente
Mykola Bogdiuk,

2
Sintassi @Ari (2) in list_initialization e (10) in operator_assignment . Il queue<T>costruttore predefinito corrisponde a un elenco di argomenti vuoto {}ed è implicito, quindi viene chiamato, quindi q1.operator=(queue<T>&&)consuma la nuova creazionequeue
Mykola Bogdiuk,

26

L'autore dell'argomento ha chiesto come cancellare la coda "in modo efficiente", quindi presumo che voglia una maggiore complessità rispetto alla O lineare (dimensione della coda) . I metodi serviti da David Rodriguez , anon hanno la stessa complessità: secondo il riferimento STL, operator =ha la complessità O (dimensione della coda) . IMHO è perché ogni elemento della coda è riservato separatamente e non è allocato in un grande blocco di memoria, come nel vettore. Quindi, per cancellare tutta la memoria, dobbiamo eliminare ogni elemento separatamente. Quindi il modo più semplice per cancellare std::queueè una riga:

while(!Q.empty()) Q.pop();

5
Non puoi semplicemente guardare alla complessità O dell'operazione se stai operando su dati reali. Vorrei prendere un O(n^2)algoritmo su un O(n)algoritmo se le costanti sull'operazione lineare lo rendono più lento del quadratico per tutti n < 2^64, a meno che non avessi qualche motivo valido per credere di dover cercare nello spazio degli indirizzi IPv6 o in qualche altro problema specifico. Le prestazioni in realtà sono più importanti per me delle prestazioni al limite.
David Stone,

2
Questa risposta migliore della risposta accettata perché la coda interna lo fa comunque quando viene distrutta. Quindi la risposta accettata è O (n) più fa allocazioni e inizializzazioni extra per la nuova coda.
Shital Shah,

Ricorda, O (n) significa minore o uguale a n complessità. Quindi, sì, in alcuni casi come la coda <vector <int>>, dovrà distruggere ogni elemento 1 per 1, che sarà lento in entrambi i modi, ma nella coda <int>, la memoria viene effettivamente allocata in un blocco grande, e quindi non ha bisogno di distruggere gli elementi interni, e quindi il distruttore della coda può usare una singola operazione free () efficiente che quasi sicuramente impiega meno di O (n) tempo.
Benjamin,

15

Apparentemente, ci sono due modi più ovvi per cancellare std::queue: scambiare con oggetto vuoto e assegnazione a oggetto vuoto.

Suggerirei di usare il compito perché è semplicemente più veloce, più leggibile e inequivocabile.

Ho misurato le prestazioni usando il seguente semplice codice e ho scoperto che lo scambio nella versione C ++ 03 funziona più lentamente del 70-80% rispetto all'assegnazione a un oggetto vuoto. In C ++ 11, tuttavia, non vi sono differenze nelle prestazioni. Comunque, andrei con un incarico.

#include <algorithm>
#include <ctime>
#include <iostream>
#include <queue>
#include <vector>

int main()
{
    std::cout << "Started" << std::endl;

    std::queue<int> q;

    for (int i = 0; i < 10000; ++i)
    {
        q.push(i);
    }

    std::vector<std::queue<int> > queues(10000, q);

    const std::clock_t begin = std::clock();

    for (std::vector<int>::size_type i = 0; i < queues.size(); ++i)
    {
        // OK in all versions
        queues[i] = std::queue<int>();

        // OK since C++11
        // std::queue<int>().swap(queues[i]);

        // OK before C++11 but slow
        // std::queue<int> empty;
        // std::swap(empty, queues[i]);
    }

    const double elapsed = double(clock() - begin) / CLOCKS_PER_SEC;

    std::cout << elapsed << std::endl;

    return 0;
}

8

In C ++ 11 puoi cancellare la coda in questo modo:

std::queue<int> queue;
// ...
queue = {};

4

È possibile creare una classe che eredita dalla coda e cancellare direttamente il contenitore sottostante. Questo è molto efficiente.

template<class T>
class queue_clearable : public std::queue<T>
{
public:
    void clear()
    {
        c.clear();
    }
};

Forse la tua implementazione consente anche al tuo oggetto Queue (qui JobQueue) di ereditare std::queue<Job>invece di avere la coda come variabile membro. In questo modo avresti accesso diretto alle c.clear()funzioni dei tuoi membri.


9
I contenitori STL non sono progettati per essere ereditati. In questo caso probabilmente stai bene perché non stai aggiungendo altre variabili membro, ma non è una buona cosa da fare in generale.
bstamour,

2

Supponendo che tu m_Queuecontenga interi:

std::queue<int>().swap(m_Queue)

Altrimenti, se contiene ad esempio puntatori a Joboggetti, allora:

std::queue<Job*>().swap(m_Queue)

In questo modo si scambia una coda vuota con la propria m_Queue, quindi m_Queuediventa vuota.


1

Preferirei non fare affidamento swap()o impostare la coda su un oggetto coda appena creato, perché gli elementi della coda non vengono distrutti correttamente. La chiamata pop()richiama il distruttore per il rispettivo oggetto elemento. Questo potrebbe non essere un problema nelle <int>code ma potrebbe avere effetti collaterali sulle code contenenti oggetti.

Pertanto un loop con while(!queue.empty()) queue.pop();sembra purtroppo essere la soluzione più efficiente almeno per le code contenenti oggetti se si desidera prevenire possibili effetti collaterali.


3
swap()oppure l'assegnazione chiama il distruttore sulla coda ormai defunta, che chiama i distruttori di tutti gli oggetti sulla coda. Ora, se la tua coda ha oggetti che sono in realtà puntatori, questo è un problema diverso, ma pop()neanche un semplice ti aiuterà.
jhfrontz,

Perché purtroppo? È elegante e semplice.
OS2

1

Faccio questo (usando C ++ 14):

std::queue<int> myqueue;
myqueue = decltype(myqueue){};

In questo modo è utile se si dispone di un tipo di coda non banale per il quale non si desidera creare un alias / typedef. Mi assicuro sempre di lasciare un commento su questo utilizzo, tuttavia, per spiegare ai programmatori ignari / manutentori che questo non è pazzo, e fatto al posto di un clear()metodo reale .


Perché dichiari esplicitamente il tipo presso l'operatore di assegnazione? Presumo che myqueue = { };funzionerà bene.
Joel Bodenmann,

0

L'uso di a unique_ptrpotrebbe essere OK.
Quindi reimpostarlo per ottenere una coda vuota e rilasciare la memoria della prima coda. Quanto alla complessità? Non ne sono sicuro, ma suppongo sia O (1).

Codice possibile:

typedef queue<int> quint;

unique_ptr<quint> p(new quint);

// ...

p.reset(new quint);  // the old queue has been destroyed and you start afresh with an empty queue

Se scegli di svuotare la coda eliminandola, va bene, ma non è questo il problema e non vedo perché unique_ptr arriva.
manuell

0

Un'altra opzione è quella di utilizzare un semplice trucco per ottenere il contenitore sottostante std::queue::ce chiamarlo clear. Questo membro deve essere presente std::queuesecondo lo standard, ma purtroppo lo è protected. L'hack qui è stato preso da questa risposta .

#include <queue>

template<class ADAPTER>
typename ADAPTER::container_type& get_container(ADAPTER& a)
{
    struct hack : ADAPTER
    {
        static typename ADAPTER::container_type& get(ADAPTER& a)
        {
            return a .* &hack::c;
        }
    };
    return hack::get(a);
}

template<typename T, typename C>
void clear(std::queue<T,C>& q)
{
    get_container(q).clear();
}

#include <iostream>
int main()
{
    std::queue<int> q;
    q.push(3);
    q.push(5);
    std::cout << q.size() << '\n';
    clear(q);
    std::cout << q.size() << '\n';
}
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.