Як ефективно очистити чергу :: std ::?


166

Я використовую std :: чергу для реалізації класу 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();
    }
}

3
Примітка dequeпідтримує ясно
bobobobo

Відповіді:


257

Загальна ідіома для очищення стандартних контейнерів - це заміна порожньою версією контейнера:

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

Це також єдиний спосіб фактичного очищення пам'яті, що знаходиться в деяких контейнерах (std :: vector)


41
Ще краще std::queue<int>().swap(q). З ідіомою копіювання та заміни все це повинно бути рівнозначним q = std::queue<int>().
Олександр К.

12
Хоча std::queue<int>().swap(q)це еквівалент вище коду, q = std::queue<int>()не повинно бути еквівалентом Оскільки у присвоєнні виділеної пам'яті немає передачі права власності, деякі контейнери (як вектор) можуть просто викликати деструктори раніше утримуваних елементів і встановити розмір (або еквівалентну операцію зі збереженими покажчиками), не звільняючи фактично пам'ять.
Девід Родрігес - дрибес

6
queueне має swap(other)методу, тому queue<int>().swap(q)не компілюється. Я думаю, ви повинні використовувати загальне swap(a, b).
Дастін Босуелл

3
@ ThorbjørnLindeijer: У C ++ 03 ви праві, у C ++ 11 черг мають своп як функцію члена, а крім того, є безкоштовне перевантаження функцій, яке буде міняти дві черги одного типу.
Девід Родрігес - дрибес

10
@ ThorbjørnLindeijer: З точки зору користувачів оригінальної черги, цих елементів не існує. Ви маєте рацію в тому, що вони будуть знищені одна за одною, а вартість лінійна, але вони не доступні іншим, крім локальної функції. У багатопотоковому середовищі ви б заблокували, поміняли тимчасову чергу з початковою, розблокували (щоб дозволити одночасний доступ) і дасте можливість заміненій черзі померти. Таким чином ви зможете перенести витрати на знищення за межі критичного розділу.
Девід Родрігес - дрибес

46

Так - трохи непорозуміння класу черги, ІМХО. Це те, що я роблю:

#include <queue>
using namespace std;;

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

8
@Naszta Будь ласка, розкажіть, наскільки swap"ефективніше"
bobobobo

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

12
q1=queue<int>();і коротше, і чіткіше (ви насправді не намагаєтесь .swap, ви намагаєтесь .clear).
бобобо

28
З новим C ++ якраз q1 = {}достатньо
Микола Богдюк

2
Синтаксис @Ari (2) при list_initialization та (10) в операторі_призначення . queue<T>Конструктор за замовчуванням відповідає порожньому списку аргументів {}і неявний, тому його називають, а потім q1.operator=(queue<T>&&)споживає новостворенийqueue
Микола Богдюк

26

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

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

5
Ви не можете просто подивитися на складність операції O, якщо ви працюєте на реальних даних. Я б взяв на себе O(n^2)алгоритм над O(n)алгоритмом, якщо константи в лінійній операції роблять це повільніше, ніж квадратичне для всіх n < 2^64, якщо я не маю серйозних причин вважати, що мені доведеться шукати адресний простір IPv6 або якусь іншу конкретну проблему. Діяльність насправді для мене важливіша, ніж продуктивність на межі.
Девід Стоун

2
Це краща відповідь, ніж прийнята відповідь, оскільки внутрішня черга робить це все одно при знищенні. Таким чином, прийнята відповідь - O (n), плюс вона робить додаткові розподіли та ініціалізації для абсолютно нової черги.
Shital Shah

Пам'ятайте, що O (n) означає менше або дорівнює складності n. Так, так, у деяких випадках, як черга <вектор <int>>, потрібно буде знищити кожен елемент 1 на 1, що буде повільно в будь-якому випадку, але в черзі <int> пам'ять фактично розподіляється в одному великому блоці, і тому йому не потрібно руйнувати внутрішні елементи, і тому деструктор черги може використовувати одну ефективну вільну () операцію, яка майже напевно займає менше, ніж O (n) часу.
Бенджамін

15

Мабуть, є два найбільш очевидних способи очищення std::queue: заміна порожнім об'єктом та призначення порожнього об'єкта.

Я б запропонував використовувати завдання, оскільки воно просто швидше, читабельніше та однозначніше.

Я вимірював продуктивність за допомогою наступного простого коду і виявив, що заміна у версії C ++ 03 працює на 70-80% повільніше, ніж присвоєння порожньому об'єкту. Однак у C ++ 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;
}

8

У C ++ 11 ви можете очистити чергу, виконавши це:

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

4

Ви можете створити клас, який успадковує з черги і безпосередньо очистити базовий контейнер. Це дуже ефективно.

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

Можливо, ваша реалізація також дозволяє об'єкту черги (тут JobQueue) успадковувати std::queue<Job>замість того, щоб мати чергу як змінну члена. Таким чином ви мали б прямий доступ до c.clear()функцій своїх членів.


9
Контейнери STL не призначені для спадкування від. У цьому випадку вам, мабуть, добре, оскільки ви не додаєте жодних додаткових змінних членів, але це взагалі не годиться.
bstamour

2

Припустимо, що m_Queueмістить цілі числа:

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

В іншому випадку, якщо він містить, наприклад, покажчики на Jobоб’єкти, то:

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

Таким чином ви поміняєте порожню чергу своєю m_Queue, тим самим m_Queueстає порожньою.


1

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

Тому петля з, while(!queue.empty()) queue.pop();здається, є найбільш ефективним рішенням принаймні для черг, що містять об'єкти, якщо ви хочете запобігти можливим побічним ефектам.


3
swap()або присвоєння викликає деструктор у теперішній час неіснуючої черги, яка викликає деструктори всіх об'єктів у черзі. Тепер, якщо у вашій черзі є об’єкти, які насправді є вказівниками, це вже інше питання - але простий pop()вам теж не допоможе.
jhfrontz

Чому на жаль? Це елегантно і просто.
OS2

1

Я роблю це (використовуючи C ++ 14):

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

Цей спосіб корисний, якщо у вас є нетривіальний тип черги, для якого ви не хочете створити псевдонім / typedef. Я завжди переконуюсь залишити коментар навколо цього використання, хоча поясню програмістам, які не підозрюють / обслуговують, що це не божевільно, а робиться замість фактичного clear()методу.


Чому ви чітко вказуєте тип у оператора призначення? Я припускаю, що це myqueue = { };буде добре.
Джоель Боденман

0

Використання unique_ptrможе бути гаразд.
Потім ви скинете її, щоб отримати порожню чергу і звільнити пам'ять першої черги. Щодо складності? Я не впевнений - але гадаю, що це 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

Якщо ви вирішите спорожнити чергу, видаливши її, це нормально, але це не питання, про що йдеться, і я не бачу, чому входить унікальний_ptr.
manuell

0

Інший варіант - використовувати простий хак, щоб отримати базовий контейнер 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';
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.