Làm cách nào để xóa std :: queue hiệu quả?


166

Tôi đang sử dụng std :: queue để triển khai lớp JobQueue. (Về cơ bản lớp học này xử lý từng công việc theo cách thức của FIFO). Trong một kịch bản, tôi muốn xóa hàng đợi trong một lần chụp (xóa tất cả các công việc khỏi hàng đợi). Tôi không thấy bất kỳ phương thức rõ ràng nào có sẵn trong lớp std :: queue.

Làm cách nào để triển khai hiệu quả phương thức rõ ràng cho lớp JobQueue?

Tôi có một giải pháp đơn giản là xuất hiện trong một vòng lặp nhưng tôi đang tìm kiếm những cách tốt hơn.

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

3
Lưu ý dequehỗ trợ rõ ràng
bobobobo

Câu trả lời:


257

Một thành ngữ phổ biến để xóa các container tiêu chuẩn là hoán đổi với một phiên bản trống của container:

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

Đây cũng là cách duy nhất để thực sự xóa bộ nhớ được giữ trong một số container (std :: vector)


41
Hơn thế nữa là std::queue<int>().swap(q). Với thành ngữ sao chép và hoán đổi, tất cả điều này sẽ tương đương với q = std::queue<int>().
Alexandre C.

12
Trong khi std::queue<int>().swap(q)tương đương với mã ở trên, q = std::queue<int>()không cần phải tương đương. Vì không có quyền chuyển giao quyền sở hữu trong việc gán bộ nhớ được phân bổ, một số bộ chứa (như vectơ) có thể chỉ cần gọi các hàm hủy của các phần tử được giữ trước đó và đặt kích thước (hoặc hoạt động tương đương với các con trỏ được lưu trữ) mà không thực sự giải phóng bộ nhớ.
David Rodríguez - dribeas

6
queuekhông có swap(other)phương thức, vì vậy queue<int>().swap(q)không biên dịch. Tôi nghĩ rằng bạn phải sử dụng chung chung swap(a, b).
Dustin Boswell

3
@ ThorbjørnLindeijer: Trong C ++ 03, bạn đã đúng, trong hàng đợi C ++ 11 có hoán đổi như một hàm thành viên và ngoài ra, có một chức năng quá tải miễn phí sẽ hoán đổi hai hàng đợi cùng loại.
David Rodríguez - dribeas 20/03 '

10
@ ThorbjørnLindeijer: Từ góc nhìn của người dùng hàng đợi ban đầu, những yếu tố đó không tồn tại. Bạn đúng ở chỗ chúng sẽ bị phá hủy lần lượt và chi phí là tuyến tính, nhưng chúng không thể truy cập được bởi bất kỳ ai khác ngoài chức năng cục bộ. Trong môi trường đa luồng, bạn sẽ khóa, trao đổi hàng đợi không tạm thời với hàng gốc, mở khóa (để cho phép truy cập đồng thời) và để hàng đợi bị tráo đổi. Bằng cách này bạn có thể di chuyển chi phí phá hủy bên ngoài phần quan trọng.
David Rodríguez - dribeas

46

Có - một chút sai lầm của lớp xếp hàng, IMHO. Đây là những gì tôi làm:

#include <queue>
using namespace std;;

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

8
@Naszta Vui lòng giải thích làm thế nào swaplà "hiệu quả hơn"
bobobobo

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

12
q1=queue<int>();vừa ngắn lại vừa rõ ràng hơn (bạn không thực sự cố gắng .swap, bạn đang cố gắng .clear).
bobobobo

28
Với C ++ mới, chỉ cần q1 = {}là đủ
Mykola Bogdiuk 21/2/2015

2
@Ari cú pháp (2) tại list_initialization và (10) tại toán tử_assocation . queue<T>{}q1.operator=(queue<T>&&)queue
Hàm

26

Tác giả của chủ đề đã hỏi làm thế nào để xóa hàng đợi một cách "hiệu quả", vì vậy tôi cho rằng anh ta muốn độ phức tạp tốt hơn so với O tuyến tính (kích thước hàng đợi) . Các phương pháp được phục vụ bởi David Rodriguez , anon có cùng độ phức tạp: theo tham chiếu STL, operator =có độ phức tạp O (kích thước hàng đợi) . IMHO bởi vì mỗi thành phần của hàng đợi được dành riêng và nó không được phân bổ trong một khối bộ nhớ lớn, như trong vector. Vì vậy, để xóa tất cả bộ nhớ, chúng ta phải xóa từng phần tử riêng biệt. Vì vậy, cách đơn giản nhất để xóa std::queuelà một dòng:

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

5
Bạn không thể chỉ nhìn vào độ phức tạp O của hoạt động nếu bạn đang vận hành trên dữ liệu thực. Tôi sẽ sử dụng O(n^2)thuật toán qua O(n)thuật toán nếu các hằng số trên hoạt động tuyến tính làm cho nó chậm hơn so với bậc hai n < 2^64, trừ khi tôi có một số lý do mạnh mẽ để tin rằng tôi phải tìm kiếm không gian địa chỉ IPv6 hoặc một số vấn đề cụ thể khác. Hiệu suất trong thực tế quan trọng đối với tôi hơn hiệu suất ở giới hạn.
David Stone

2
Câu trả lời này tốt hơn câu trả lời được chấp nhận bởi vì hàng đợi nội bộ thực hiện điều này dù sao đi nữa khi bị phá hủy. Vì vậy, câu trả lời được chấp nhận là O (n) cộng với việc phân bổ và khởi tạo thêm cho hàng đợi hoàn toàn mới.
Shital Shah

Hãy nhớ rằng, O (n) có nghĩa nhỏ hơn hoặc bằng độ phức tạp n. Vì vậy, vâng, trong một số trường hợp như hàng đợi <vector <int >>, nó sẽ cần phải hủy từng phần tử 1, sẽ chậm, nhưng trong hàng đợi <int>, bộ nhớ thực sự được phân bổ trong một khối lớn, và do đó, nó không cần phải phá hủy các phần tử bên trong và do đó, hàm hủy của hàng đợi có thể sử dụng một hoạt động miễn phí () hiệu quả duy nhất mà gần như chắc chắn mất ít thời gian hơn O (n).
Benjamin

15

Rõ ràng, có hai cách rõ ràng nhất để xóa std::queue: hoán đổi với đối tượng trống và gán cho đối tượng trống.

Tôi sẽ đề nghị sử dụng bài tập vì đơn giản là nó nhanh hơn, dễ đọc hơn và không mơ hồ.

Tôi đã đo hiệu suất bằng cách sử dụng mã đơn giản sau và tôi thấy rằng việc hoán đổi trong phiên bản C ++ 03 hoạt động chậm hơn 70-80% so với gán cho một đối tượng trống. Tuy nhiên, trong C ++ 11 không có sự khác biệt về hiệu năng. Dù sao, tôi sẽ đi với sự phân công.

#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

Trong C ++ 11, bạn có thể xóa hàng đợi bằng cách thực hiện việc này:

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

4

Bạn có thể tạo một lớp kế thừa từ hàng đợi và xóa vùng chứa bên dưới trực tiếp. Điều này rất hiệu quả.

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

Có thể việc triển khai của bạn cũng cho phép đối tượng Hàng đợi của bạn (ở đây JobQueue) kế thừa std::queue<Job>thay vì có hàng đợi là biến thành viên. Bằng cách này, bạn sẽ có quyền truy cập trực tiếp c.clear()vào các chức năng thành viên của bạn.


9
Container STL không được thiết kế để được kế thừa từ. Trong trường hợp này, bạn có thể ổn vì bạn không thêm bất kỳ biến thành viên nào, nhưng nói chung đó không phải là một điều tốt để làm.
bstamour

2

Giả sử m_Queuesố nguyên của bạn chứa:

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

Mặt khác, nếu nó chứa ví dụ con trỏ tới Jobcác đối tượng, thì:

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

Bằng cách này, bạn trao đổi một hàng đợi trống với bạn m_Queue, do đó m_Queuetrở nên trống rỗng.


1

Tôi không muốn dựa vào swap()hoặc đặt hàng đợi thành một đối tượng hàng đợi mới được tạo, bởi vì các thành phần hàng đợi không bị phá hủy đúng cách. Gọi pop()gọi hàm hủy cho đối tượng phần tử tương ứng. Điều này có thể không phải là một vấn đề trong <int>hàng đợi nhưng rất có thể có tác dụng phụ trên hàng đợi chứa các đối tượng.

Do đó, một vòng lặp while(!queue.empty()) queue.pop();dường như không may là giải pháp hiệu quả nhất ít nhất là cho các hàng đợi chứa các đối tượng nếu bạn muốn ngăn chặn các tác dụng phụ có thể xảy ra.


3
swap()hoặc gán gọi hàm hủy trên hàng đợi không còn tồn tại, gọi hàm hủy của tất cả các đối tượng trên hàng đợi. Bây giờ, nếu hàng đợi của bạn có các đối tượng thực sự là con trỏ, thì đó là một vấn đề khác - nhưng đơn giản pop()sẽ không giúp bạn ở đó.
jhfrontz

Tại sao không may? Thật thanh lịch và đơn giản.
OS2

1

Tôi làm điều này (Sử dụng C ++ 14):

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

Cách này hữu ích nếu bạn có loại hàng đợi không tầm thường mà bạn không muốn tạo bí danh / typedef cho. Tuy nhiên, tôi luôn đảm bảo để lại một bình luận xung quanh việc sử dụng này, để giải thích cho các lập trình viên không nghi ngờ / bảo trì rằng điều này không điên rồ, và được thực hiện thay cho một clear()phương pháp thực tế .


Tại sao bạn nói rõ loại tại toán tử gán? Tôi cho rằng myqueue = { };sẽ làm việc tốt.
Joel Bodenmann

0

Sử dụng unique_ptrcó thể là OK.
Sau đó, bạn đặt lại nó để có được một hàng đợi trống và giải phóng bộ nhớ của hàng đợi đầu tiên. Như sự phức tạp? Tôi không chắc chắn - nhưng đoán đó là O (1).

Mã có thể:

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

Nếu bạn chọn làm trống hàng đợi bằng cách xóa nó, điều đó không sao, nhưng đó không phải là câu hỏi và tôi không hiểu tại sao unique_ptr lại xuất hiện.
manuell

0

Một tùy chọn khác là sử dụng một hack đơn giản để lấy container bên dưới std::queue::cvà gọi clearnó. Thành viên này phải có mặt std::queuetheo tiêu chuẩn, nhưng thật không may protected. Việc hack ở đây được lấy từ câu trả lời này .

#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';
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.