如何有效清除std :: queue?


166

我正在使用std :: queue来实现JobQueue类。(基本上,此类以FIFO方式处理每个作业)。在一种情况下,我想一次清除队列(从队列中删除所有作业)。我在std :: queue类中看不到任何清晰的方法。

如何有效实现JobQueue类的clear方法?

我有一个循环弹出的简单解决方案,但我正在寻找更好的方法。

//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

Answers:


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>()
Alexandre C.

12
虽然std::queue<int>().swap(q)与上面的代码等效,但q = std::queue<int>()不必等效。由于在分配的内存的分配中没有所有权的转移,因此某些容器(例如向量)可能只调用先前保存的元素的析构函数并设置大小(或使用存储的指针进行的等效操作),而没有实际释放内存。
DavidRodríguez-dribeas 2010年

6
queue没有swap(other)方法,所以queue<int>().swap(q)不编译。我认为您必须使用泛型swap(a, b)
达斯汀·

3
@ThorbjørnLindeijer:在C ++ 03中,您是对的,在C ++ 11中,队列确实具有swap作为成员函数,此外还有一个自由函数重载,它将交换两个相同类型的队列。
大卫·罗德里格斯(DavidRodríguez)-德里贝斯

10
@ThorbjørnLindeijer:从原始队列的用户的角度来看,这些元素不存在。您的说法是正确的,因为它们将被一个接一个地销毁并且成本是线性的,但是除了本地函数以外,其他任何人都无法访问它们。在多线程环境中,您将锁定,将非临时队列与原始队列交换,解锁(以允许并发访问)并让交换的队列死亡。这样,您可以将销毁成本转移到关键部分之外。
大卫·罗德里格斯(DavidRodríguez)-德里贝斯2012年

46

是的-队列类恕我直言有点不妥当。这是我的工作:

#include <queue>
using namespace std;;

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

8
@Naszta请详细说明swap“更有效”的方法
bobobobo

@bobobobo:q1.swap(queue<int>());
纳斯塔

12
q1=queue<int>();既短,更清晰的(你不是真的.swap,你想.clear)。
bobobobo

28
有了新的C ++,就q1 = {}足够了
Mykola Bogdiuk

2
@Ari语法(2)在list_initialization和(10)在operator_assignment。默认queue<T>构造函数匹配空参数列表{}并且是隐式的,因此将其调用,然后q1.operator=(queue<T>&&)使用新创建的对象queue
Mykola Bogdiuk

26

该主题的作者问如何“有效”清除队列,所以我认为他要比线性更好的复杂性。 O(queue size)David Rodriguez匿名)提供的方法具有相同的复杂度:根据STL参考,operator =具有复杂度O(queue size)。恕我直言,这是因为队列的每个元素都是单独保留的,并且没有像矢量一样分配在一个大内存块中。因此,要清除所有内存,我们必须分别删除每个元素。因此,最直接的清除方法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个复杂度。因此,是的,在某些情况下,例如queue <vector <int >>,它需要将每个元素1破坏1,这两种方式都会很慢,但是在queue <int>中,内存实际上是在一个大块中分配的,因此不需要破坏内部元素,因此队列的析构函数可以使用单个有效的free()操作,该操作几乎肯定会花费不到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();
    }
};

也许您的实现还允许您的Queue对象(在此处JobQueue)继承,std::queue<Job>而不是将队列作为成员变量。这样,您将可以直接访问c.clear()成员函数。


9
STL容器不旨在被继承。在这种情况下,您可能还可以,因为您没有添加任何其他成员变量,但这通常不是一件好事。
bstamour


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 = { };会很好。
Joel Bodenmann

0

使用a 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

如果您选择清空删除它的队列,这没什么,但是这并不是问题是关于什么的,我不明白为什么的unique_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';
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.