Как эффективно очистить std:: queue?

Я использую std:: queue для реализации класса JobQueue. (В основном этот класс обрабатывает каждое задание в режиме FIFO). В одном сценарии я хочу очистить очередь одним выстрелом (удалить все задания из очереди). Я не вижу никакого ясного метода, доступного в классе std:: queue.

Как эффективно реализовать прозрачный метод для класса JobQueue?

У меня есть одно простое решение в цикле, но я ищу лучшие способы.

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

Ответ 1

Обычная идиома для очистки стандартных контейнеров заменяется пустой версией контейнера:

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

Это также единственный способ фактически очистить память, хранящуюся внутри некоторых контейнеров (std::vector)

Ответ 2

Да - немного недостоверности класса очереди, ИМХО. Это то, что я делаю:

#include <queue>
using namespace std;;

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

Ответ 3

Автор темы спросил, как "эффективно" очистить очередь, поэтому я предполагаю, что он хочет лучшей сложности, чем линейный O (размер очереди). Методы, обслуживаемый Дэвид Родригес, Anon имеют одинаковую сложность: в соответствии со справочной STL, operator = имеет сложность O (размер очереди). ИМХО, потому что каждый элемент очереди зарезервирован отдельно и он не размещен в одном большом блоке памяти, как в векторе. Таким образом, чтобы очистить всю память, мы должны удалить каждый элемент отдельно. Так что самый простой способ очистить std::queue - это одна строка:

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

Ответ 4

По-видимому, существует два наиболее очевидных способа очистки std::queue: замена с пустым объектом и назначение пустого объекта.

Я бы предложил использовать назначение, потому что он просто быстрее, читабельнее и недвусмысленно.

Я измерил производительность, используя следующий простой код, и я обнаружил, что обмен в версии С++ 03 работает на 70-80% медленнее, чем назначение пустого объекта. Однако в С++ 11 нет никакой разницы в производительности. Во всяком случае, я бы пошел с назначением.

#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;
}

Ответ 5

В С++ 11 вы можете очистить очередь, выполнив это:

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

Ответ 6

Вы можете создать класс, который наследует от очереди, и сразу очистить базовый контейнер. Это очень эффективно.

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

Возможно, ваша реализация также позволяет вашему объекту Queue (здесь JobQueue) наследовать std::queue<Job> вместо очереди в качестве переменной-члена. Таким образом, у вас будет прямой доступ к c.clear() в ваших функциях-членах.

Ответ 7

Я бы предпочел не полагаться на swap() или устанавливать очередь на вновь созданный объект очереди, потому что элементы очереди не были должным образом уничтожены. Вызов pop() вызывает деструктор для соответствующего элемента элемента. Возможно, это не проблема в очередях <int>, но может иметь побочные эффекты для очередей, содержащих объекты.

Поэтому цикл с while(!queue.empty()) queue.pop();, к сожалению, является наиболее эффективным решением, по крайней мере, для очередей, содержащих объекты, если вы хотите предотвратить возможные побочные эффекты.

Ответ 8

Предполагая, что ваш m_Queue содержит целые числа:

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

В противном случае, если он содержит, например, указатели на объекты Job, то:

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

Таким образом, вы m_Queue пустую очередь на m_Queue, поэтому m_Queue становится пустым.

Ответ 9

Использование unique_ptr может быть в порядке.
Затем вы получите reset, чтобы получить пустую очередь и освободить память первой очереди. Что касается сложности? Я не уверен, но думаю, что это O (1).

Возможный код:

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

Ответ 10

Я делаю это (используя С++ 14):

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

Этот способ полезен, если у вас нетривиальный тип очереди, для которого вы не хотите создавать псевдоним /typedef. Тем не менее, я всегда оставляю комментарий об этом использовании, чтобы объяснить ничего не подозревающим/обслуживающим программистам, что это не сумасшествие, а сделано вместо реального метода clear().

Ответ 11

Другим вариантом является использование простого хака для получения базового контейнера std::queue::c и вызова clear для него. Этот член должен присутствовать в std::queue согласно стандарту, но, к сожалению, protected. Взлом здесь был взят из этого ответа.

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