Tại sao std :: queue :: pop không trả về giá trị.?


123

Tôi đã xem qua trang này nhưng tôi không thể lấy được lý do tương tự. Ở đó nó được đề cập rằng

"hợp lý hơn nếu nó không trả về giá trị nào và yêu cầu khách hàng sử dụng front () để kiểm tra giá trị ở đầu hàng đợi"

Nhưng việc kiểm tra một phần tử từ front () cũng yêu cầu phần tử đó phải được sao chép trong lvalue. Ví dụ trong đoạn mã này

std::queue<int> myqueue;
int myint;
int result;
std::cin >> myint;
myqueue.push (myint);

/ * ở đây tạm thời sẽ được tạo trên RHS sẽ được gán cho kết quả, và trong trường hợp nếu trả về bằng tham chiếu thì kết quả sẽ không hợp lệ sau thao tác bật * /

result = myqueue.front();  //result.
std::cout << ' ' << result;
myqueue.pop();

trên đối tượng cout dòng thứ năm đầu tiên tạo một bản sao của myqueue.front () sau đó gán nó cho kết quả. Vì vậy, sự khác biệt là gì, chức năng pop có thể làm điều tương tự.


Bởi vì được thực hiện theo cách này (tức là, void std::queue::pop();).
101010

Câu hỏi đã được trả lời, nhưng như một sidenote: nếu bạn thực sự muốn có một cửa sổ pop mà trở về, nó có thể dễ dàng thực hiện với một chức năng miễn phí: ideone.com/lnUzf6
eerorika

1
Liên kết của bạn là đến tài liệu STL. Nhưng bạn đang hỏi về thư viện chuẩn C ++. Những thứ khác.
juanchopanza

5
"Nhưng việc kiểm tra một phần tử từ front()cũng yêu cầu phần tử đó phải được sao chép trong giá trị" - không, không. fronttrả về một tham chiếu, không phải một giá trị. Bạn có thể kiểm tra giá trị mà nó đề cập đến mà không cần sao chép nó.
Mike Seymour,

1
@KeminZhou mô hình bạn mô tả yêu cầu một bản sao. Có lẽ. Nếu bạn muốn cộng dồn mức tiêu thụ của hàng đợi thì có, bạn phải tạo một bản sao trước khi giải phóng khóa trên hàng đợi. Tuy nhiên, nếu bạn chỉ quan tâm đến việc tách đầu vào và đầu ra, thì bạn không cần khóa để kiểm tra mặt trước. Bạn có thể đợi khóa cho đến khi sử dụng xong và cần gọi pop(). Nếu bạn sử dụng std::queue<T, std::list<T>>thì không lo ngại về việc tham chiếu được cung cấp khỏi front()bị vô hiệu bởi a push(). Nhưng bạn phải biết cách sử dụng của mình và nên ghi lại các ràng buộc của bạn.
jwm 14/09/18

Câu trả lời:


105

Vì vậy, sự khác biệt là gì, chức năng pop có thể đã làm điều tương tự.

Nó thực sự có thể đã làm điều tương tự. Lý do mà nó không, là vì một cửa sổ bật lên trả về phần tử được bật lên không an toàn khi có các ngoại lệ (phải trả về theo giá trị và do đó tạo ra một bản sao).

Hãy xem xét tình huống này (với một triển khai pop ngây thơ / được tạo ra, để củng cố quan điểm của tôi):

template<class T>
class queue {
    T* elements;
    std::size_t top_position;
    // stuff here
    T pop()
    {
        auto x = elements[top_position];
        // TODO: call destructor for elements[top_position] here
        --top_position;  // alter queue state here
        return x;        // calls T(const T&) which may throw
    }

Nếu hàm tạo bản sao của T ném trở lại, bạn đã thay đổi trạng thái của hàng đợi ( top_positiontrong cách triển khai ngây thơ của tôi) và phần tử bị xóa khỏi hàng đợi (và không được trả lại). Đối với tất cả các ý định và mục đích (bất kể bạn bắt ngoại lệ bằng cách nào trong mã máy khách), phần tử ở đầu hàng đợi sẽ bị mất.

Việc triển khai này cũng không hiệu quả trong trường hợp bạn không cần giá trị được bật lên (tức là nó tạo ra một bản sao của phần tử mà không ai sẽ sử dụng).

Điều này có thể được thực hiện một cách an toàn và hiệu quả, với hai hoạt động riêng biệt ( void popconst T& front()).


hmmm ... có ý nghĩa
cbinder

37
C ++ 11 lưu ý: nếu T có một phương thức khởi tạo di chuyển không chấp nhận rẻ tiền (trường hợp này thường xảy ra đối với loại đối tượng được đặt trong ngăn xếp) thì việc trả về theo giá trị là hiệu quả và an toàn ngoại lệ.
Roman L

9
@ DavidRodríguez-dribeas: Tôi không đề xuất bất kỳ thay đổi nào, quan điểm của tôi là một số nhược điểm đã đề cập ít trở thành vấn đề với C ++ 11.
Roman L

11
Nhưng tại sao nó được gọi là pop? điều đó khá phản trực quan. Nó có thể được đặt tên drop, và sau đó thì rõ ràng cho mọi người rằng nó không bật yếu tố, nhưng giọt nó thay vì ...
UeliDeSchwert

12
@utnapistim: thao tác "pop" trên thực tế luôn là lấy phần tử trên cùng từ một ngăn xếp và trả lại. Khi tôi lần đầu tiên xem các ngăn xếp STL, tôi đã rất ngạc nhiên, ít nhất phải nói rằng, về việc popkhông trả lại bất kỳ thứ gì. Xem ví dụ trên wikipedia .
Cris Luengo

34

Trang bạn đã liên kết để trả lời câu hỏi của bạn.

Để trích dẫn toàn bộ phần có liên quan:

Người ta có thể thắc mắc tại sao pop () trả về void, thay vì value_type. Đó là, tại sao người ta phải sử dụng front () và pop () để kiểm tra và loại bỏ phần tử ở phía trước hàng đợi, thay vì kết hợp cả hai trong một hàm thành viên? Trên thực tế, có một lý do chính đáng cho thiết kế này. Nếu pop () trả về phần tử phía trước, nó sẽ phải trả về theo giá trị thay vì tham chiếu: trả về bằng tham chiếu sẽ tạo ra một con trỏ treo lơ lửng. Tuy nhiên, trả về theo giá trị không hiệu quả: nó liên quan đến ít nhất một lệnh gọi hàm tạo bản sao dự phòng. Vì pop () không thể trả lại giá trị theo cách vừa hiệu quả vừa chính xác, nên hợp lý hơn là nó không trả về giá trị nào và yêu cầu khách hàng sử dụng front () để kiểm tra giá trị tại mặt trước của hàng đợi.

C ++ được thiết kế với tính hiệu quả, vượt quá số dòng mã mà lập trình viên phải viết.


16
Có lẽ, nhưng lý do thực sự là không thể triển khai phiên bản an toàn ngoại lệ (với bảo đảm mạnh mẽ) cho phiên bản poptrả về giá trị.
James Kanze,

5

pop không thể trả về một tham chiếu đến giá trị bị xóa, vì nó đang bị xóa khỏi cấu trúc dữ liệu, vậy tham chiếu phải tham chiếu đến giá trị nào? Nó có thể trả về theo giá trị, nhưng nếu kết quả của pop không được lưu trữ ở đâu thì sao? Sau đó, lãng phí thời gian sao chép giá trị một cách không cần thiết.


4
Lý do thực sự là an toàn ngoại lệ. Không có cách nào an toàn để có một "giao dịch" với ngăn xếp (hoặc phần tử vẫn ở trên ngăn xếp hoặc nó được trả lại cho bạn) nếu poptrả về và hành động trả lại có thể dẫn đến ngoại lệ. Rõ ràng là nó sẽ phải xóa phần tử trước khi trả lại nó, và sau đó nếu có thứ gì đó ném phần tử đó có thể bị mất không thể phục hồi.
Jon

1
Liệu mối quan tâm này có còn áp dụng với fi a noexcept move constructor không? (Tất nhiên 0 bản là hiệu quả hơn mà hai di chuyển, nhưng điều đó có thể mở cửa cho một ngoại lệ an toàn, efficent kết hợp phía trước + pop)
Peppe

1
@peppe, bạn có thể trả về theo giá trị nếu bạn biết value_typecó hàm tạo di chuyển nothrow, nhưng giao diện hàng đợi sau đó sẽ khác tùy thuộc vào loại đối tượng bạn lưu trữ trong đó, điều này sẽ không hữu ích.
Jonathan Wakely,

1
@peppe: Điều đó sẽ an toàn, nhưng ngăn xếp là một lớp thư viện chung và do đó nó càng phải giả định về loại phần tử thì nó càng ít hữu ích.
Jon

Tôi biết STL sẽ không cho phép điều đó, nhưng điều đó có nghĩa là người ta có thể xây dựng lớp hàng đợi riêng của mình với blackjack và ^ W ^ W ^ W với tính năng này :)
Peppe

3

Với việc triển khai hiện tại, điều này là hợp lệ:

int &result = myqueue.front();
std::cout << result;
myqueue.pop();

Nếu pop sẽ trả về một tham chiếu, như thế này:

value_type& pop();

Sau đó, mã sau có thể bị lỗi, vì tham chiếu không còn hợp lệ nữa:

int &result = myqueue.pop();
std::cout << result;

Mặt khác, nếu nó sẽ trả về một giá trị trực tiếp:

value_type pop();

Sau đó, bạn sẽ cần tạo một bản sao để mã này hoạt động, mã này kém hiệu quả hơn:

int result = myqueue.pop();
std::cout << result;

1

Bắt đầu từ C ++ 11, có thể lưu trữ hành vi mong muốn bằng cách sử dụng ngữ nghĩa di chuyển. Thích pop_and_move. Vì vậy, hàm tạo bản sao sẽ không được gọi và hiệu suất sẽ chỉ phụ thuộc vào hàm tạo di chuyển.


2
Không, ngữ nghĩa chuyển động không làm cho popngoại lệ an toàn một cách kỳ diệu .
LF

0

Bạn hoàn toàn có thể làm được điều này:

std::cout << ' ' << myqueue.front();

Hoặc, nếu bạn muốn giá trị trong một biến, hãy sử dụng tham chiếu:

const auto &result = myqueue.front();
if (result > whatever) do_whatever();
std::cout << ' ' << result;

Bên cạnh đó: từ ngữ 'hợp lý hơn' là một dạng chủ quan của việc 'chúng tôi đã xem xét các kiểu sử dụng và thấy cần nhiều sự phân tách hơn'. (Hãy yên tâm: ngôn ngữ C ++ phát triển không hề nhẹ ...)


0

Tôi nghĩ giải pháp tốt nhất là thêm một số thứ như

std::queue::pop_and_store(value_type& value);

trong đó giá trị sẽ nhận giá trị được bật lên.

Ưu điểm là nó có thể được thực hiện bằng cách sử dụng toán tử gán di chuyển, trong khi sử dụng front + pop sẽ tạo một bản sao.

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.