Como limpo a fila std :: com eficiência?

166

Estou usando std :: fila para implementar a classe JobQueue. (Basicamente, essa classe processa cada trabalho da maneira FIFO). Em um cenário, desejo limpar a fila de uma só vez (exclua todos os trabalhos da fila). Não vejo nenhum método claro disponível na classe std :: queue.

Como implemento eficientemente o método claro para a classe JobQueue?

Eu tenho uma solução simples de aparecer em um loop, mas estou procurando maneiras melhores.

//Clears the job queue
void JobQueue ::clearJobs()
 {
  // I want to avoid pop in a loop
    while (!m_Queue.empty())
    {
        m_Queue.pop();
    }
}
aJ.
fonte
3
Nota dequesuporta clara
bobobobo

Respostas:

257

Um idioma comum para limpar contêineres padrão é trocar por uma versão vazia do contêiner:

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

Também é a única maneira de limpar a memória mantida dentro de alguns contêineres (std :: vector)

David Rodríguez - dribeas
fonte
41
Melhor ainda é std::queue<int>().swap(q). Com o idioma copy e swap, tudo isso deve ser equivalente a q = std::queue<int>().
Alexandre C.
12
Embora std::queue<int>().swap(q)seja equivalente ao código acima, q = std::queue<int>()não precisa ser equivalente. Como não há transferência de propriedade na atribuição da memória alocada, alguns contêineres (como vetor) podem chamar os destruidores dos elementos mantidos anteriormente e definir o tamanho (ou operação equivalente com os ponteiros armazenados) sem realmente liberar a memória.
David Rodríguez - dribeas
6
queuenão possui um swap(other)método, portanto queue<int>().swap(q)não compila. Eu acho que você tem que usar o genérico swap(a, b).
Dustin Boswell
3
@ ThorbjørnLindeijer: No C ++ 03, você está certo, nas filas do C ++ 11 são trocadas como função membro e, além disso, há uma sobrecarga de função livre que trocará duas filas do mesmo tipo.
David Rodríguez - dribeas 20/03
10
@ ThorbjørnLindeijer: Do ponto de vista dos usuários da fila original, esses elementos não existem. Você está certo, pois eles serão destruídos um após o outro e o custo é linear, mas não poderão ser acessados ​​por ninguém além da função local. Em um ambiente multithread, você trava, troca uma fila não temporária pela original, desbloqueia (para permitir acessos simultâneos) e deixa a fila trocada morrer. Dessa forma, você pode mover o custo da destruição para fora da seção crítica.
David Rodríguez - dribeas 27/03
46

Sim - um pouco de uma característica incorreta da classe da fila, IMHO. Isto é o que eu faço:

#include <queue>
using namespace std;;

int main() {
    queue <int> q1;
    // stuff
    q1 = queue<int>();  
}
Mark Ransom
fonte
8
@Naszta Por favor explique como swapé "mais eficaz"
bobobobo
@bobobobo:q1.swap(queue<int>());
Naszta
12
q1=queue<int>();é mais curto e mais claro (você não está realmente tentando .swap, está tentando .clear).
Bobobobo 24/05
28
Com o novo C ++, apenas q1 = {}é suficiente
Mykola Bogdiuk
2
@Ari sintaxe (2) em list_initialization e (10) em operator_assignment . O queue<T>construtor padrão corresponde à lista de argumentos vazia {}e está implícito, por isso é chamado e, em seguida, q1.operator=(queue<T>&&)consome o recém-criadoqueue
Mykola Bogdiuk
26

O autor do tópico perguntou como limpar a fila "eficientemente", então suponho que ele queira uma complexidade melhor que O linear (tamanho da fila) . Métodos servidos por David Rodriguez , Anon tem a mesma complexidade: de acordo com a referência STL, operator =tem complexidade O (fila de tamanho) . IMHO é porque cada elemento da fila é reservado separadamente e não é alocado em um grande bloco de memória, como no vetor. Portanto, para limpar toda a memória, precisamos excluir todos os elementos separadamente. Portanto, a maneira mais direta de limpar std::queueé uma linha:

while(!Q.empty()) Q.pop();
janis
fonte
5
Você não pode apenas olhar para a complexidade O da operação se estiver operando com dados reais. Eu usaria um O(n^2)algoritmo sobre um O(n)algoritmo se as constantes na operação linear o tornassem mais lento que o quadrático para todos n < 2^64, a menos que eu tivesse algum motivo forte para acreditar que tinha que pesquisar no espaço de endereço IPv6 ou algum outro problema específico. O desempenho na realidade é mais importante para mim do que o desempenho no limite.
David Stone
2
Essa resposta é melhor que a aceita porque a fila interna faz isso de qualquer maneira quando é destruída. Portanto, a resposta aceita é O (n) e faz alocações e inicializações extras para uma nova fila.
Shital Shah
Lembre-se, O (n) significa menor ou igual a n complexidade. Portanto, sim, em alguns casos, como a fila <vector <int>>, será necessário destruir cada elemento 1 por 1, o que será lento de qualquer maneira, mas na fila <int>, a memória é realmente alocada em um grande bloco, e, portanto, ele não precisa destruir os elementos internos; portanto, o destruidor da fila pode usar uma única operação free () eficiente que quase certamente leva menos de O (n) tempo.
Benjamin
15

Aparentemente, existem duas maneiras mais óbvias de limpar std::queue: trocar com objeto vazio e designar para objeto vazio.

Eu sugeriria usar a atribuição porque ela é simplesmente mais rápida, mais legível e inequívoca.

Avaliei o desempenho usando o código simples a seguir e descobri que a troca na versão C ++ 03 funciona 70-80% mais lenta que a atribuição a um objeto vazio. No C ++ 11, no entanto, não há diferença no desempenho. Enfim, eu iria com atribuição.

#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;
}
tim
fonte
8

No C ++ 11, você pode limpar a fila fazendo o seguinte:

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

Você pode criar uma classe que herda da fila e limpar o contêiner subjacente diretamente. Isto é muito eficiente.

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

Talvez sua implementação também permita que o objeto Fila (aqui JobQueue) seja herdado em std::queue<Job>vez de ter a fila como uma variável membro. Dessa forma, você teria acesso direto às c.clear()suas funções de membro.

typ1232
fonte
9
Os contêineres STL não foram projetados para serem herdados. Nesse caso, você provavelmente está bem porque não está adicionando nenhuma variável de membro adicional, mas não é uma boa coisa a fazer em geral.
precisa saber é o seguinte
2

Supondo que você m_Queuecontenha números inteiros:

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

Caso contrário, se ele contiver, por exemplo, ponteiros para Jobobjetos, então:

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

Dessa forma, você troca uma fila vazia pelo seu e m_Queue, assim, m_Queuefica vazia.

Dániel László Kovács
fonte
1

Prefiro não confiar swap()ou definir a fila para um objeto de fila criado recentemente, porque os elementos da fila não são destruídos adequadamente. A chamada pop()chama o destruidor para o respectivo objeto do elemento. Isso pode não ser um problema nas <int>filas, mas pode muito bem ter efeitos colaterais nas filas que contêm objetos.

Portanto, while(!queue.empty()) queue.pop();infelizmente, um loop com parece ser a solução mais eficiente, pelo menos para filas contendo objetos, se você deseja evitar possíveis efeitos colaterais.

Marste
fonte
3
swap()ou atribuição chama o destruidor na fila agora extinta, que chama os destruidores de todos os objetos na fila. Agora, se sua fila tiver objetos que realmente são ponteiros, isso é um problema diferente - mas um simples pop()também não ajudará você.
precisa saber é o seguinte
Porque infelizmente? É elegante e simples.
OS2 17/01
1

Eu faço isso (usando C ++ 14):

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

Dessa forma, é útil se você tiver um tipo de fila não trivial para o qual não deseja criar um alias / typedef. Eu sempre certifique-se de deixar um comentário sobre esse uso, no entanto, para explicar aos programadores desavisados ​​/ de manutenção que isso não é loucura e feito em vez de um clear()método real .

void.pointer
fonte
Por que você declara explicitamente o tipo no operador de atribuição? Presumo que myqueue = { };funcionará bem.
Joel Bodenmann 29/01
0

Usar um unique_ptrpode estar OK.
Você o redefine para obter uma fila vazia e liberar a memória da primeira fila. Quanto à complexidade? Não tenho certeza - mas acho que é O (1).

Código possível:

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
Ronnie
fonte
Se você optar por esvaziar a fila por excluí-lo, isso é OK, mas não é isso que a pergunta é sobre, e eu não vejo por que unique_ptr entra.
Manuell
0

Outra opção é usar um hack simples para obter o contêiner subjacente std::queue::ce acessá clear-lo. Esse membro deve estar presente de std::queueacordo com o padrão, mas infelizmente está protected. O hack aqui foi retirado desta resposta .

#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';
}
Ruslan
fonte