ฉันจะล้าง std :: queue อย่างมีประสิทธิภาพได้อย่างไร


166

ฉันใช้ std :: queue สำหรับการใช้คลาส JobQueue (โดยทั่วไปคลาสนี้ดำเนินการแต่ละงานในลักษณะ FIFO) ในสถานการณ์สมมติหนึ่งฉันต้องการล้างคิวในนัดเดียว (ลบงานทั้งหมดออกจากคิว) ฉันไม่เห็นวิธีการที่ชัดเจนใด ๆ ที่มีอยู่ใน std :: queue class

ฉันจะใช้วิธีการที่ชัดเจนสำหรับคลาส 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>()ด้วยการคัดลอกและแลกเปลี่ยนสำนวนทั้งหมดนี้ควรจะเทียบเท่ากับ
Alexandre C.

12
ในขณะที่std::queue<int>().swap(q)เทียบเท่ากับรหัสข้างต้นq = std::queue<int>()ไม่จำเป็นต้องเทียบเท่า เนื่องจากไม่มีการถ่ายโอนความเป็นเจ้าของในการกำหนดหน่วยความจำที่จัดสรรบางคอนเทนเนอร์ (เช่นเวกเตอร์) อาจเรียก destructors ขององค์ประกอบที่ถือครองไว้ก่อนหน้านี้และกำหนดขนาด (หรือการทำงานเทียบเท่ากับพอยน์เตอร์ที่เก็บไว้)
David Rodríguez - dribeas

6
queueไม่มีswap(other)วิธีการดังนั้นqueue<int>().swap(q)อย่ารวบรวม swap(a, b)ฉันคิดว่าคุณต้องใช้ทั่วไป
ดัสตินบอสเวล

3
@ ThorbjørnLindeijer: ใน C ++ 03 คุณถูกต้องใน C ++ 11 คิวจะมีการสลับเป็นฟังก์ชันสมาชิกและนอกจากนี้ยังมีฟังก์ชั่นฟรีเกินพิกัดที่จะสลับสองคิวของประเภทเดียวกัน
David Rodríguez - dribeas

10
@ ThorbjørnLindeijer: จากมุมมองของผู้ใช้คิวเดิมองค์ประกอบเหล่านั้นไม่มีอยู่จริง คุณถูกต้องในที่ที่พวกเขาจะถูกทำลายหนึ่งหลังจากที่อื่นและค่าใช้จ่ายเป็นเชิงเส้น แต่พวกเขาไม่สามารถเข้าถึงได้โดยคนอื่นนอกเหนือจากฟังก์ชั่นในท้องถิ่น ในสภาพแวดล้อมแบบมัลติเธรดคุณจะล็อกสลับคิวที่ไม่ใช่ชั่วคราวกับต้นฉบับดั้งเดิมปลดล็อก (เพื่ออนุญาตการเข้าถึงพร้อมกัน) และปล่อยให้คิวสลับตาย วิธีนี้คุณสามารถย้ายค่าใช้จ่ายในการทำลายล้างออกไปนอกส่วนที่สำคัญ
David Rodríguez - dribeas

46

ใช่ - ความผิดพลาดเล็กน้อยของคลาสคิว IMHO นี่คือสิ่งที่ฉันทำ:

#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)
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 (คิวขนาด) วิธีการเสิร์ฟโดยเดวิด Rodriguez , อานนท์มีความซับซ้อนเดียวกันตามการอ้างอิง STL, operator =มีความซับซ้อนO (คิวขนาด) IMHO เป็นเพราะแต่ละองค์ประกอบของคิวถูกจองแยกต่างหากและไม่ได้ถูกจัดสรรในบล็อกหน่วยความจำขนาดใหญ่หนึ่งบล็อกเหมือนในเวกเตอร์ ดังนั้นเพื่อล้างหน่วยความจำทั้งหมดเราจะต้องลบทุกองค์ประกอบแยกกัน ดังนั้นวิธีที่ชัดเจนที่สุดstd::queueคือหนึ่งบรรทัด:

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

5
คุณไม่สามารถดูความซับซ้อนของการดำเนินการ O หากคุณใช้งานข้อมูลจริง ฉันจะใช้O(n^2)อัลกอริทึมมากกว่าO(n)อัลกอริทึมหากค่าคงที่ในการดำเนินการเชิงเส้นทำให้มันช้ากว่ากำลังสองสำหรับทุกคนn < 2^64ยกเว้นว่าฉันมีเหตุผลที่เชื่อได้ว่าฉันต้องค้นหาพื้นที่ที่อยู่ IPv6 หรือปัญหาเฉพาะอื่น ๆ การแสดงในความเป็นจริงสำคัญกับฉันมากกว่าการแสดงที่ขีด จำกัด
David Stone

2
คำตอบนี้ดีกว่าคำตอบที่ยอมรับได้เนื่องจากคิวภายในทำเช่นนี้เมื่อถูกทำลาย ดังนั้นคำตอบที่ได้รับการยอมรับคือ O (n) รวมทั้งเป็นการจัดสรรและกำหนดค่าเริ่มต้นเพิ่มเติมสำหรับคิวใหม่
Shital Shah

จำไว้ว่า O (n) หมายถึงความซับซ้อนน้อยกว่าหรือเท่ากับ n ดังนั้นใช่ในบางกรณีเช่นคิว <vector <int>> มันจะต้องทำลายแต่ละองค์ประกอบที่ 1 ด้วย 1 ซึ่งจะช้าลงด้วยวิธีใดวิธีหนึ่ง แต่ในคิว <int> หน่วยความจำจริง ๆ จะถูกจัดสรรในบล็อกขนาดใหญ่หนึ่งบล็อก และดังนั้นจึงไม่จำเป็นต้องทำลายองค์ประกอบภายในและตัวทำลายล้างของคิวจึงสามารถใช้การดำเนินการ free () ที่มีประสิทธิภาพเดียวซึ่งเกือบจะใช้เวลาน้อยกว่า O (n)
Benjamin

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


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()ใช้ destructor สำหรับวัตถุองค์ประกอบที่เกี่ยวข้อง สิ่งนี้อาจไม่เป็นปัญหาใน<int>คิว แต่อาจมีผลข้างเคียงกับคิวที่มีวัตถุ

ดังนั้นการวนซ้ำด้วยwhile(!queue.empty()) queue.pop();ดูเหมือนว่าน่าจะเป็นทางออกที่มีประสิทธิภาพที่สุดอย่างน้อยสำหรับคิวที่มีวัตถุถ้าคุณต้องการป้องกันผลข้างเคียงที่อาจเกิดขึ้นได้


3
swap()หรือการมอบหมายเรียก destructor บนคิวที่หมดอายุตอนนี้ซึ่งเรียก destructors ของวัตถุทั้งหมดในคิว ทีนี้ถ้าคิวของคุณมีวัตถุที่เป็นตัวชี้จริงนั่นเป็นปัญหาที่แตกต่าง - แต่สิ่งที่ง่าย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';
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.