Std :: kuyruğunu nasıl verimli bir şekilde temizleyebilirim?


166

JobQueue sınıfını uygulamak için std :: queue kullanıyorum. (Temel olarak bu sınıf her işi FIFO tarzında işler). Bir senaryoda, tek çekimde kuyruğu temizlemek istiyorum (kuyruktaki tüm işleri silmek). Std :: queue sınıfında herhangi bir net yöntem görmüyorum.

JobQueue sınıfı için net yöntemi nasıl verimli bir şekilde uygularım?

Bir döngü içinde haşhaş basit bir çözüm var ama daha iyi yollar arıyorum.

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

3
Not açıkdeque destekler
bobobobo

Yanıtlar:


257

Standart kapları temizlemek için yaygın bir deyim, kabın boş bir versiyonuyla değiştirilmesidir:

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

Aynı zamanda bazı kapların içinde tutulan belleği temizlemenin tek yoludur (std :: vector)


41
Daha da iyisi std::queue<int>().swap(q). Kopyalama ve takas deyimiyle bunların hepsi eşdeğer olmalıdır q = std::queue<int>().
Alexandre

12
std::queue<int>().swap(q)Yukarıdaki koda eşdeğer olsa da , eşdeğer olması q = std::queue<int>()gerekmez. Tahsis edilen belleğin tahsisinde mülkiyet devri olmadığından, bazı kaplar (vektör gibi) daha önce tutulan elemanların yıkıcılarını çağırabilir ve belleği gerçekten serbest bırakmadan boyutu (veya depolanan işaretçilerle eşdeğer işlemi) ayarlayabilir .
David Rodríguez - dribeas

6
queuebir swap(other)yöntemi yok, bu yüzden queue<int>().swap(q)derlemiyor. Bence jenerik kullanmak zorundasınız swap(a, b).
Dustin Boswell

3
@ ThorbjørnLindeijer: C ++ 03'te haklısınız, C ++ 11 kuyruklarında üye işlevi olarak takas var ve ayrıca aynı türdeki iki kuyruğu değiştirecek ücretsiz bir işlev aşırı yükü var.
David Rodríguez - dribeas

10
@ ThorbjørnLindeijer: Orijinal kuyruğun kullanıcıları açısından bu öğeler mevcut değildir. Birbiri ardına imha edileceği ve maliyetin doğrusal olacağı konusunda haklısınız, ancak yerel işlevden başka kimse tarafından erişilemez. Çok iş parçacıklı bir ortamda kilitler, geçici olmayan bir kuyruğu orijinali ile değiştirir, kilidini açar (eşzamanlı erişime izin vermek için) ve değiştirilen kuyruğun ölmesine izin verirsiniz. Bu şekilde yıkım maliyetini kritik bölümün dışına taşıyabilirsiniz.
David Rodríguez - dribeas

46

Evet - sıra sınıfının biraz yanlış bir özelliği, IMHO. Bu benim işim:

#include <queue>
using namespace std;;

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

8
@Naszta Lütfen swap"daha etkili" nasıl detaylandırılır
bobobobo

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

12
q1=queue<int>();hem daha kısa hem de daha net ( gerçekten denemiyorsunuz .swap, deniyorsunuz .clear).
bobobobo

28
Yeni C ++ q1 = {}ile yeter
Mykola Bogdiuk

2
@Ari sözdizimi (2) list_initialization ve (10) operatör_assignment . Varsayılan queue<T>yapıcı boş argüman listesiyle eşleşir {}ve q1.operator=(queue<T>&&)queue
örtüktür

26

Konunun yazarı kuyruğu nasıl "verimli" temizlemek için sordu, bu yüzden o doğrusal O (kuyruk boyutu) daha iyi karmaşıklık istediğini varsayalım . David Rodriguez tarafından sunulan yöntemler , anon ile aynı karmaşıklığa sahiptir: STL referansına göre, Ooperator = karmaşıklığına (kuyruk boyutu) sahiptir . IMHO, kuyruğun her bir öğesinin ayrı ayrı ayrılması ve vektördeki gibi büyük bir bellek bloğuna ayrılmamasıdır. Bu nedenle, tüm belleği temizlemek için her öğeyi ayrı olarak silmeliyiz. Temizlemenin en düz yolu std::queuebir satırdır:

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

5
Gerçek veriler üzerinde çalışıyorsanız, işlemin O karmaşıklığına bakamazsınız. Ben IPv6 adres alanı veya başka bir özel sorun aramak zorunda inanmak için güçlü bir neden olmadıkça , doğrusal işlem sabitleri herkes için ikinci dereceden daha yavaş yaparsa bir O(n^2)algoritma üzerine bir O(n)algoritma alacaktı n < 2^64. Gerçekte performans benim için sınırdaki performanstan daha önemlidir.
David Stone

2
Bu, kabul edilen cevaptan daha iyi bir cevap çünkü dahili kuyruk bunu yok ederken zaten yapıyor. Kabul edilen cevap O (n) artı yeni kuyruk için ekstra tahsisler ve başlatmalar yapar.
Shital Shah

Unutmayın, O (n) n karmaşıklığına eşit veya daha az demektir. Bu nedenle, evet, <vector <int>> gibi bazı durumlarda, her iki öğeyi de tek tek yok etmek gerekir, bu her iki şekilde yavaş olacaktır, ancak <int> kuyruğunda bellek aslında büyük bir blokta tahsis edilir, ve böylece iç öğeleri yok etmek gerekmez ve böylece kuyruğun yıkıcısı neredeyse kesinlikle O (n) zamanından daha az süren tek bir verimli serbest () işlemi kullanabilir.
Benjamin

15

Görünüşe göre, temizlemek için en belirgin iki yol var std::queue: boş nesneyle değiştirme ve boş nesneye atama.

Ödevin kullanılmasını öneririm çünkü daha hızlı, daha okunaklı ve açık.

Aşağıdaki basit kodu kullanarak performansı ölçtüm ve C ++ 03 sürümünde değiştirmenin boş bir nesneye atamadan% 70-80 daha yavaş çalıştığını gördüm. Bununla birlikte, C ++ 11'de performansta bir fark yoktur. Her neyse, görevle giderdim.

#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'de bunu yaparak kuyruğu temizleyebilirsiniz:

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

4

Kuyruktan devralan bir sınıf oluşturabilir ve temeldeki kapsayıcıyı doğrudan temizleyebilirsiniz. Bu çok verimlidir.

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

Belki de bir uygulamanız , kuyruğa üye değişkeni olarak sahip olmak yerine Kuyruk nesnenizin (burada JobQueue) devralmasına da izin verir std::queue<Job>. Bu şekilde c.clear()üye işlevlerinizde doğrudan erişime sahip olursunuz.


9
STL kapları kalıtsal olarak tasarlanmamıştır. Bu durumda, muhtemelen herhangi bir ek üye değişkeni eklemediğiniz için sorun olmaz, ancak genel olarak yapılması iyi bir şey değildir.
bstamour

2

m_Queueİçerdiği tam sayıları varsayarsak :

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

Aksi takdirde, örneğin Jobnesnelere işaretçiler içeriyorsa , o zaman:

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

Bu şekilde, boş bir kuyruğu ile değiştirirsiniz m_Queue, böylece m_Queueboş olur.


1

swap()Kuyruk öğeleri düzgün şekilde yok edilmediğinden, kuyruğa yeni oluşturulan bir kuyruk nesnesine güvenmemeyi veya ayarlamayı tercih etmem . Çağrı pop(), ilgili eleman nesnesi için yıkıcıyı çağırır. Bu, <int>kuyruklarda bir sorun olmayabilir, ancak nesne içeren kuyruklar üzerinde çok iyi yan etkilere sahip olabilir.

Bu nedenle, while(!queue.empty()) queue.pop();olası bir yan etkiyi önlemek istiyorsanız, en azından nesne içeren kuyruklar için en etkili çözüm olan bir döngü ne yazık ki görünüyor.


3
swap()veya atama, sıradaki tüm nesnelerin yıkıcılarını çağıran şimdi yok olan sıradaki yıkıcıyı çağırır. Şimdi, sıranızda gerçekten işaret eden nesneler varsa, bu farklı bir konudur - ancak basit bir pop()de size yardımcı olmaz.
16:24

Neden maalesef? Zarif ve basit.
OS2

1

Bunu (C ++ 14 kullanarak) yapmak:

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

Bu şekilde, takma ad / typedef oluşturmak istemediğiniz önemsiz olmayan bir kuyruk türünüz varsa kullanışlıdır. Yine de, şüphesiz / bakım programcılarına bunun deli olmadığını ve gerçek bir clear()yöntem yerine yapıldığını açıklamak için her zaman bu kullanım hakkında bir yorum bıraktığımdan emin olun .


Atama operatöründe türü neden açıkça belirtiyorsunuz? Bunun myqueue = { };iyi çalışacağını varsayıyorum .
Joel Bodenmann

0

A kullanmak uygun unique_ptrolabilir.
Daha sonra boş bir kuyruk elde etmek ve ilk kuyruğun belleğini serbest bırakmak için sıfırlarsınız. Karmaşıklığa gelince? Emin değilim - ama sanırım O (1).

Olası kod:

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

Kuyruğu silerek boşaltmayı seçerseniz, sorun değil, ancak sorunun nedeni bu değil ve unique_ptr'in neden geldiğini anlamıyorum.
manuell

0

Başka bir seçenek, temel kap almak std::queue::cve çağırmak cleariçin basit bir kesmek kullanmaktır . Bu üye std::queuestandarda göre bulunmalıdır , ancak maalesef protected. Buradaki kesmek bu cevaptan alındı .

#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';
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.