Bagaimana cara menghapus std :: antrian secara efisien?

166

Saya menggunakan std :: antrian untuk mengimplementasikan kelas JobQueue. (Pada dasarnya kelas ini memproses setiap pekerjaan dengan cara FIFO). Dalam satu skenario, saya ingin menghapus antrian dalam satu kesempatan (hapus semua pekerjaan dari antrian). Saya tidak melihat metode yang jelas tersedia di std :: kelas antrian.

Bagaimana cara saya menerapkan metode yang jelas untuk kelas JobQueue secara efisien?

Saya punya satu solusi sederhana untuk muncul dalam satu lingkaran tetapi saya mencari cara yang lebih baik.

//Clears the job queue
void JobQueue ::clearJobs()
 {
  // I want to avoid pop in a loop
    while (!m_Queue.empty())
    {
        m_Queue.pop();
    }
}
aJ.
sumber
3
Note dequemendukung clear
bobobobo

Jawaban:

257

Ungkapan umum untuk membersihkan wadah standar adalah menukar dengan versi kosong dari wadah:

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

Ini juga satu-satunya cara untuk benar-benar membersihkan memori yang tersimpan di dalam beberapa wadah (std :: vector)

David Rodríguez - dribeas
sumber
41
Lebih baik lagi std::queue<int>().swap(q). Dengan idiom copy dan swap, semua ini harus setara dengan q = std::queue<int>().
Alexandre C.
12
Meskipun std::queue<int>().swap(q)setara dengan kode di atas, q = std::queue<int>()tidak perlu sama. Karena tidak ada transfer kepemilikan dalam penugasan memori yang dialokasikan beberapa wadah (seperti vektor) mungkin hanya memanggil destruktor dari elemen yang dimiliki sebelumnya dan mengatur ukuran (atau operasi setara dengan pointer yang disimpan) tanpa benar-benar melepaskan memori.
David Rodríguez - dribeas
6
queuetidak memiliki swap(other)metode, jadi queue<int>().swap(q)jangan kompilasi. Saya pikir Anda harus menggunakan obat generik swap(a, b).
Dustin Boswell
3
@ ThorbjørnLindeijer: Di C ++ 03 Anda benar, di C ++ 11 antrian memiliki swap sebagai fungsi anggota, dan juga ada kelebihan fungsi bebas yang akan menukar dua antrian dengan tipe yang sama.
David Rodríguez - dribeas
10
@ ThorbjørnLindeijer: Dari perspektif pengguna antrian asli, elemen-elemen itu tidak ada. Anda benar bahwa mereka akan dihancurkan satu demi satu dan biayanya linear, tetapi mereka tidak dapat diakses oleh orang lain selain fungsi lokal. Dalam lingkungan multithreaded, Anda akan mengunci, menukar antrian non-sementara dengan yang asli, membuka kunci (untuk memungkinkan akses bersamaan) dan membiarkan antrian yang ditukar mati. Dengan cara ini Anda dapat memindahkan biaya kehancuran di luar bagian kritis.
David Rodríguez - dribeas
46

Ya - sedikit kesalahan kelas antrian, IMHO. Inilah yang saya lakukan:

#include <queue>
using namespace std;;

int main() {
    queue <int> q1;
    // stuff
    q1 = queue<int>();  
}
Mark tebusan
sumber
8
@Naszta Tolong jelaskan bagaimana swap"lebih efektif"
bobobobo
@bobobobo:q1.swap(queue<int>());
Naszta
12
q1=queue<int>();keduanya lebih pendek, dan lebih jelas (Anda tidak benar - benar berusaha .swap, Anda mencoba .clear).
bobobobo
28
Dengan C ++ yang baru, q1 = {}cukup
Mykola Bogdiuk
2
Sintaks @Ari (2) di list_initialization dan (10) di operator_assignment . queue<T>Konstruktor default cocok dengan daftar argumen kosong {}dan implisit, sehingga disebut, kemudian q1.operator=(queue<T>&&)mengkonsumsi yang baru dibuatqueue
Mykola Bogdiuk
26

Penulis topik bertanya bagaimana menghapus antrian "efisien", jadi saya menganggap dia ingin kompleksitas yang lebih baik daripada O linear (ukuran antrian) . Metode yang dilayani oleh David Rodriguez , anon memiliki kompleksitas yang sama: menurut referensi STL, operator =memiliki kompleksitas O (ukuran antrian) . IMHO karena setiap elemen antrian dicadangkan secara terpisah dan tidak dialokasikan dalam satu blok memori besar, seperti dalam vektor. Jadi untuk menghapus semua memori, kita harus menghapus setiap elemen secara terpisah. Jadi cara paling mudah untuk menghapus std::queueadalah satu baris:

while(!Q.empty()) Q.pop();
janis
sumber
5
Anda tidak bisa hanya melihat kompleksitas operasi O jika Anda beroperasi pada data nyata. Saya akan mengambil O(n^2)algoritma dari suatu O(n)algoritma jika konstanta pada operasi linier membuatnya lebih lambat dari kuadrat untuk semua n < 2^64, kecuali saya punya alasan kuat untuk percaya saya harus mencari ruang alamat IPv6 atau beberapa masalah khusus lainnya. Kinerja pada kenyataannya lebih penting bagi saya daripada kinerja pada batasnya.
David Stone
2
Jawaban ini lebih baik daripada jawaban yang diterima karena secara internal melakukan hal ini ketika dihancurkan. Jadi jawaban yang diterima adalah O (n) plus itu tidak alokasi tambahan dan inisialisasi untuk antrian baru.
Shital Shah
Ingat, O (n) berarti kurang dari atau sama dengan n kompleksitas. Jadi, ya, dalam beberapa kasus seperti antrian <vektor <int>>, perlu untuk menghancurkan setiap elemen 1 demi 1, yang akan lambat, tetapi dalam antrian <int>, memori sebenarnya dialokasikan dalam satu blok besar, dan karena itu tidak perlu menghancurkan elemen-elemen internal, dan karenanya destructor antrian dapat menggunakan satu operasi bebas () efisien yang hampir pasti memakan waktu kurang dari O (n) waktu.
Benjamin
15

Rupanya, ada dua cara yang paling jelas untuk dihapus std::queue: bertukar dengan objek kosong dan penugasan ke objek kosong.

Saya akan menyarankan menggunakan tugas karena hanya lebih cepat, lebih mudah dibaca, dan tidak ambigu.

Saya mengukur kinerja menggunakan kode sederhana berikut dan saya menemukan bahwa bertukar dalam versi C ++ 03 bekerja 70-80% lebih lambat daripada penugasan ke objek kosong. Namun, dalam C ++ 11 tidak ada perbedaan kinerja. Bagaimanapun, saya akan pergi dengan tugas.

#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
sumber
8

Di C ++ 11 Anda dapat menghapus antrian dengan melakukan ini:

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

Anda bisa membuat kelas yang mewarisi dari antrian dan menghapus wadah yang mendasarinya secara langsung. Ini sangat efisien.

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

Mungkin implementasi Anda juga memungkinkan objek Antrian Anda (di sini JobQueue) untuk mewarisi std::queue<Job>alih-alih memiliki antrian sebagai variabel anggota. Dengan cara ini Anda akan memiliki akses langsung ke c.clear()dalam fungsi anggota Anda.

typ1232
sumber
9
Wadah STL tidak dirancang untuk diwarisi. Dalam hal ini Anda mungkin baik-baik saja karena Anda tidak menambahkan variabel anggota tambahan, tetapi itu bukan hal yang baik untuk dilakukan secara umum.
bstamour
2

Dengan asumsi Anda m_Queuemengandung bilangan bulat:

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

Jika tidak, jika berisi mis pointer ke Jobobjek, maka:

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

Dengan cara ini Anda menukar antrian kosong dengan Anda m_Queue, sehingga m_Queuemenjadi kosong.

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

Saya lebih suka tidak mengandalkan swap()atau mengatur antrian ke objek antrian yang baru dibuat, karena elemen antrian tidak dihancurkan dengan benar. Memanggil pop()memanggil destruktor untuk objek elemen masing-masing. Ini mungkin bukan masalah dalam <int>antrian tetapi mungkin sangat baik memiliki efek samping pada antrian yang berisi objek.

Oleh karena itu loop dengan while(!queue.empty()) queue.pop();tampaknya sayangnya menjadi solusi paling efisien setidaknya untuk antrian yang berisi objek jika Anda ingin mencegah kemungkinan efek samping.

Marste
sumber
3
swap()atau tugas memanggil destruktor pada antrian sekarang-mati, yang memanggil destruktor semua objek pada antrian. Sekarang, jika antrian Anda memiliki objek yang sebenarnya pointer, itu masalah yang berbeda - tetapi sederhana pop()tidak akan membantu Anda di sana.
jhfrontz
Mengapa sayangnya? Elegan dan sederhana.
OS2
1

Saya melakukan ini (Menggunakan C ++ 14):

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

Cara ini berguna jika Anda memiliki jenis antrian non-sepele yang Anda tidak ingin buat alias / typedef. Saya selalu memastikan untuk meninggalkan komentar seputar penggunaan ini, untuk menjelaskan kepada programmer yang tidak curiga bahwa ini tidak gila, dan dilakukan sebagai pengganti clear()metode yang sebenarnya .

void.pointer
sumber
Mengapa Anda secara eksplisit menyatakan jenisnya di operator penugasan? Saya berasumsi itu myqueue = { };akan bekerja dengan baik.
Joel Bodenmann
0

Menggunakan unique_ptrmungkin OK.
Anda kemudian mengatur ulang untuk mendapatkan antrian kosong dan melepaskan memori antrian pertama. Adapun kompleksitasnya? Saya tidak yakin - tetapi kira itu O (1).

Kode yang mungkin:

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
sumber
Jika Anda memilih untuk mengosongkan antrian dengan menghapusnya, tidak apa-apa, tapi bukan itu yang menjadi pertanyaan, dan saya tidak melihat mengapa unique_ptr masuk.
manuell
0

Pilihan lain adalah menggunakan peretasan sederhana untuk mendapatkan wadah yang mendasarinya std::queue::cdan memanggilnya clear. Anggota ini harus hadir std::queuesesuai standar, tetapi sayangnya protected. Retasan di sini diambil dari jawaban ini .

#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
sumber