2015-10-02 15 views
11

Khi đọc Eric Niebler's range proposal,
Tôi đã xem thuật ngữ sentinel để thay thế cho trình lặp kết thúc.
Tôi đang gặp khó khăn trong việc hiểu các lợi ích của sentinel qua trình lặp kết thúc.
Ai đó có thể cung cấp một ví dụ rõ ràng về những gì sentintel mang đến cho bảng không thể thực hiện được với các cặp lặp chuẩn?Sự khác nhau giữa một phiên bản nội tuyến và một trình lặp kết thúc là gì?

"Một sentinel là một sự trừu tượng của một iterator qua-the-end. Sentinel là loại thường xuyên có thể được sử dụng để biểu thị sự kết thúc của một phạm vi. Một sentinel và một iterator biểu thị một phạm vi trách nhiệm Một sentinel biểu thị một phần tử khi một iterator i so sánh với sentinel, và tôi trỏ đến phần tử đó. " - N4382

Tôi nghĩ rằng sentinels hoạt động như các hàm trong việc xác định kết thúc của một phạm vi, thay vì chỉ vị trí?

Trả lời

10

Sentinel chỉ đơn giản cho phép trình lặp kết thúc có loại khác.

Các thao tác được phép trên trình lặp vòng lặp kết thúc bị giới hạn, nhưng điều này không được phản ánh trong loại của nó. Nó không phải là ok để * một .end() iterator, nhưng trình biên dịch sẽ cho phép bạn.

Một sentinel không có dereference unary, hoặc ++, trong số những thứ khác. Nó thường được giới hạn như các trình vòng lặp yếu nhất qua trình lặp cuối, nhưng được thực thi tại thời gian biên dịch.

Có một khoản hoàn trả. Thường phát hiện trạng thái kết thúc dễ hơn là tìm nó. Với một sentinel, == có thể gửi đến "phát hiện nếu đối số khác đã qua kết thúc" tại thời gian biên dịch, thay vì thời gian chạy.

Kết quả là một số mã được sử dụng để chậm hơn so với tương đương C hiện đang biên dịch xuống tốc độ cấp C, chẳng hạn như sao chép chuỗi bị chấm dứt bằng cách sử dụng std::copy. Nếu không có sentinels, bạn phải quét để tìm kết thúc trước bản sao, hoặc vượt qua trong vòng lặp với một cờ bool nói "Tôi là người gửi cuối" (hoặc tương đương), và kiểm tra nó trên ==.

Có những lợi thế tương tự khác khi làm việc với các phạm vi đếm dựa.Ngoài ra, một số thứ như phạm vi zip trở nên dễ dàng hơn để thể hiện (mã zip cuối cùng có thể chứa cả hai mã thông báo nguồn và trả về bằng nhau nếu một trong hai sentinel thực hiện: zip iterators hoặc chỉ so sánh vòng lặp đầu tiên hoặc so sánh cả hai). Một cách khác để suy nghĩ về nó là các thuật toán có xu hướng không sử dụng toàn bộ khái niệm lặp của tham số trên tham số được truyền qua như trình vòng lặp kết thúc, và trình vòng lặp đó được xử lý theo cách khác nhau trong thực tế. Quay lại đầu trang Sentinel có nghĩa là người gọi có thể khai thác thực tế đó, từ đó cho phép trình biên dịch khai thác nó dễ dàng hơn.


Một loạt zip là những gì bạn nhận được khi bạn bắt đầu với 2 hoặc nhiều phạm vi, và "zip" chúng lại với nhau giống như một dây kéo. Phạm vi hiện nay vượt quá số lượng các yếu tố phạm vi riêng lẻ. Việc thúc đẩy một trình vòng lặp zip tiến lên từng bộ lặp "chứa", và ditto để dereferencing và so sánh.

+0

phạm vi zip là gì? – Walter

+0

@walter thêm chú thích – Yakk

+0

Hmm.Nhưng một trình vòng lặp zip không thể là một RandomAccessIterator, vì tham chiếu 'của nó không giống với' value_type & '. có thể không hoạt động (như bạn đã nói [chính mình] (http://stackoverflow.com/a/32871002/1023390)) Vì vậy, nó là gì tốt cho? – Walter

2

Sentinels và vòng lặp kết thúc tương tự ở chỗ chúng đánh dấu sự kết thúc của một dải ô. Chúng khác nhau về cách mà kết thúc được phát hiện; hoặc bạn đang thử nghiệm bản thân trình lặp hoặc bạn đang kiểm tra giá trị dữ liệu tại trình lặp. Nếu bạn đã thực hiện các thử nghiệm trên dữ liệu, một sentinel có thể cho phép thuật toán của bạn hoàn thành "miễn phí" mà không cần bất kỳ thử nghiệm bổ sung nào. Điều này có thể đơn giản hóa mã, hoặc làm cho nó nhanh hơn.

Một sentinel rất phổ biến là số không byte được sử dụng để đánh dấu sự kết thúc của một chuỗi. Không cần phải giữ một trình lặp riêng biệt cho phần cuối của chuỗi, nó có thể được xác định khi bạn làm việc với các ký tự của chính chuỗi đó. Nhược điểm của quy ước này là một chuỗi không thể chứa ký tự 0.

Lưu ý rằng tôi đã viết câu trả lời này trước khi đọc đề xuất trong liên kết; đây là định nghĩa cổ điển của một sentinel mà có thể không đồng ý với định nghĩa được đề xuất ở đó.

+2

Chúng cũng hữu ích khi bạn viết mã trong Python sẽ được gọi là máy phát. Điểm cuối không được biết và có thể không được biết (ví dụ: ['istream_iterator'] (http://www.cplusplus.com/reference/iterator/istream_iterator/) gói một ổ cắm hoặc đường ống, nơi bạn thắng ' Sentinel mô tả trạng thái logic của "đang hoàn thành" hơn là một kịch bản phụ thuộc và định nghĩa cụ thể của hoàn thành (ví dụ: khi hoàn thành khi trình vòng lặp đạt 'vector.begin()' + 'vector.size()') – ShadowRanger

0

Động cơ trung tâm để giới thiệu một sentinel là có rất nhiều hoạt động lặp được hỗ trợ nhưng thường không bao giờ cần thiết cho end-iterator end(). Ví dụ, hầu như không có bất kỳ điểm nào trong dereferencing nó thông qua *end(), trong incrementing nó qua ++end(), và như vậy (*).

Ngược lại, cách sử dụng chính của end() chỉ đơn thuần là so sánh nó với một trình biến đổi it để báo hiệu liệu it có ở cuối thứ mà nó chỉ lặp lại hay không. Và, như thường lệ trong lập trình, các yêu cầu khác nhau và ứng dụng khác nhau đề xuất một loại mới.

Thư viện dãy-v3 biến quan sát này thành một giả định (được thực hiện thông qua một khái niệm): nó giới thiệu một kiểu mới cho end() và chỉ yêu cầu nó tương đương với trình lặp tương ứng - nhưng không yêu cầu các thao tác lặp thông thường). Loại mới này của end() được gọi là sentinel.

Lợi thế chính ở đây là trừu tượng thu được và phân tách mối quan tâm tốt hơn, dựa trên đó trình biên dịch có thể thực hiện tối ưu hóa tốt hơn. Trong mã, ý tưởng cơ bản là điều này (đây chỉ là giải thích và không liên quan gì đến thư viện phạm vi-v3):

struct my_iterator; //some iterator 
struct my_sentinel 
{ 
    bool is_at_end(my_iterator it) const 
    { 
     //here implement the logic when the iterator is at the end 
    } 
}; 

auto operator==(my_iterator it, my_sentinel s) //also for (my_sentinel s, my_iterator it) 
{ 
    return s.is_at_end(it); 
} 

Xem tóm tắt? Bây giờ, bạn có thể thực hiện bất kỳ kiểm tra mà bạn muốn trong is_at_end chức năng, ví dụ:

  • dừng bao giờ (nhận được một phạm vi vô hạn)
  • dừng sau N increments (để có được một loạt tính)
  • dừng khi một \0 gặp phải, tức là *it = '\0' (để lặp qua chuỗi C)
  • dừng khi đó là 12'o đồng hồ (để ăn trưa), v.v.

Hơn nữa, liên quan đến hiệu suất, người ta có thể sử dụng thông tin thời gian biên dịch trong séc (ví dụ: nghĩ về số N ở trên làm thông số biên dịch). Trong trường hợp này, trình biên dịch có thể có khả năng tối ưu hóa mã tốt hơn.


(*) Lưu ý rằng điều này không có nghĩa là nói chung không sử dụng cho loại hoạt động này.Ví dụ: --end() có thể hữu ích ở một số nơi, xem ví dụ: this question. Tuy nhiên, dường như có thể thực hiện thư viện chuẩn mà không có những thư viện này - đây là thư viện phạm vi-v3 đã làm.

Các vấn đề liên quan