Một nhịp span là gì và khi nào tôi nên sử dụng một?


236

Gần đây, tôi đã nhận được đề xuất sử dụng span<T>mã trong mã của mình hoặc đã thấy một số câu trả lời ở đây trên trang web sử dụng span- được cho là một loại container. Nhưng - tôi không thể tìm thấy bất cứ thứ gì như thế trong thư viện chuẩn C ++ 17.

Vậy điều bí ẩn này là gì span<T>và tại sao (hoặc khi nào) nên sử dụng nó nếu nó không chuẩn?


std::spanđã được đề xuất vào năm 2017. Nó áp dụng cho C ++ 17 hoặc C ++ 20. Đồng thời xem P0122R5, span: khung nhìn an toàn cho các chuỗi đối tượng . Bạn có thực sự muốn nhắm mục tiêu ngôn ngữ đó? Nó sẽ là nhiều năm trước khi trình biên dịch bắt kịp.
jww

6
@jww: span là khá có thể sử dụng với C ++ 11 ... gsl::spanthay vì std::span. Xem thêm câu trả lời của tôi dưới đây.
einpoklum

Cũng được ghi lại trên cppreference.com: en.cppreference.com/w/cpp/container/span
Keith Thompson

1
@KeithThndry: Không phải vào năm 2017, đó không phải là ...
einpoklum

@jww Tất cả các trình biên dịch hỗ trợ std :: span <> hiện ở chế độ C ++ 20. Và span có sẵn từ nhiều libs bên thứ 3. Bạn đã đúng - đó là năm: chính xác là 2 năm.
Contango

Câu trả lời:


271

Nó là gì?

A span<T>là:

  • Một sự trừu tượng rất nhẹ của một chuỗi các giá trị liên tục Tở đâu đó trong bộ nhớ.
  • Về cơ bản a struct { T * ptr; std::size_t length; }với một loạt các phương pháp thuận tiện.
  • Loại không sở hữu (nghĩa là "loại tham chiếu" thay vì "loại giá trị"): Nó không bao giờ phân bổ cũng không giải quyết bất cứ điều gì và không giữ cho con trỏ thông minh tồn tại.

Nó trước đây được gọi là một array_viewvà thậm chí sớm hơn như array_ref.

Khi nào tôi nên sử dụng nó?

Đầu tiên, khi không sử dụng nó:

  • Đừng sử dụng nó trong mã mà chỉ có thể lấy bất kỳ cặp bắt đầu và kết thúc vòng lặp, như std::sort, std::find_if, std::copyvà tất cả những chức năng templated siêu generic.
  • Không sử dụng nó nếu bạn có một thùng chứa thư viện tiêu chuẩn (hoặc thùng chứa Boost, v.v.) mà bạn biết là phù hợp với mã của bạn. Nó không có ý định thay thế bất kỳ ai trong số họ.

Bây giờ khi nào thực sự sử dụng nó:

Sử dụng span<T>(tương ứng span<const T>) thay vì tự do T*(tương ứng const T*) mà bạn có giá trị độ dài. Vì vậy, thay thế các chức năng như:

  void read_into(int* buffer, size_t buffer_size);

với:

  void read_into(span<int> buffer);

Tại sao tôi nên sử dụng nó? Tại sao nó là một điều tốt?

Oh, nhịp là tuyệt vời! Sử dụng span...

  • có nghĩa là bạn có thể làm việc với tổ hợp con trỏ + chiều dài / bắt đầu + kết thúc con trỏ giống như bạn làm với một thư viện thư viện tiêu chuẩn được tạo ra, ưa thích, ví dụ:

    • for (auto& x : my_span) { /* do stuff */ }
    • std::find_if(my_span.begin(), my_span.end(), some_predicate);

    ... nhưng hoàn toàn không có lớp trên cùng nào phát sinh.

  • cho phép trình biên dịch làm nhiều việc hơn cho bạn đôi khi. Ví dụ: cái này:

    int buffer[BUFFER_SIZE];
    read_into(buffer, BUFFER_SIZE);

    trở thành thế này:

    int buffer[BUFFER_SIZE];
    read_into(buffer);

    ... sẽ làm những gì bạn muốn nó làm. Xem thêm Hướng dẫn P.5 .

  • là lựa chọn hợp lý để chuyển const vector<T>&đến các chức năng khi bạn mong muốn dữ liệu của mình liền kề trong bộ nhớ. Không còn bị mắng bởi các bậc thầy C ++ cao lớn và hùng mạnh!

  • tạo điều kiện cho phân tích tĩnh, vì vậy trình biên dịch có thể giúp bạn bắt được các lỗi ngớ ngẩn.
  • cho phép thiết bị biên dịch gỡ lỗi để kiểm tra giới hạn thời gian chạy (tức là spancác phương thức của sẽ có một số mã kiểm tra giới hạn trong #ifndef NDEBUG... #endif)
  • chỉ ra rằng mã của bạn (đang sử dụng nhịp) không sở hữu bộ nhớ trỏ.

Thậm chí còn có nhiều động lực hơn để sử dụng spans, mà bạn có thể tìm thấy trong hướng dẫn cốt lõi của C ++ - nhưng bạn bắt kịp sự trôi dạt.

Tại sao nó không có trong thư viện chuẩn (kể từ C ++ 17)?

Nó nằm trong thư viện chuẩn - nhưng chỉ là của C ++ 20. Lý do là nó vẫn còn khá mới ở dạng hiện tại, được hình thành cùng với dự án hướng dẫn cốt lõi C ++ , mới chỉ hình thành từ năm 2015. (Mặc dù như các nhà bình luận đã chỉ ra, nó có lịch sử sớm hơn.)

Vậy làm cách nào để sử dụng nó nếu nó chưa có trong thư viện chuẩn?

Đó là một phần của Thư viện hỗ trợ của Nguyên tắc cốt lõi (GSL). Triển khai:

  • GSL của Microsoft / Neil Macintosh chứa một triển khai độc lập:gsl/span
  • GSL-Lite là một triển khai tiêu đề duy nhất của toàn bộ GSL (không quá lớn, đừng lo lắng), bao gồm cả span<T>.

Việc triển khai GSL thường đảm nhận một nền tảng thực hiện hỗ trợ C ++ 14 [ 14 ]. Các triển khai tiêu đề đơn thay thế này không phụ thuộc vào các cơ sở GSL:

Lưu ý rằng các triển khai nhịp khác nhau này có một số khác biệt về phương thức / chức năng hỗ trợ mà chúng đi kèm; và chúng cũng có thể khác một chút so với phiên bản đi vào thư viện chuẩn C ++ 20.


Đọc thêm: Bạn có thể tìm thấy tất cả các chi tiết và cân nhắc thiết kế trong đề xuất chính thức cuối cùng trước C ++ 17, P0122R7: span: giới hạn an toàn cho các chuỗi đối tượng của Neal Macintosh và Stephan J. Lavavej. Mặc dù nó hơi dài. Ngoài ra, trong C ++ 20, ngữ nghĩa so sánh nhịp đã thay đổi (theo bài viết ngắn này của Tony van Eerd).


2
Sẽ hợp lý hơn khi chuẩn hóa một phạm vi chung (hỗ trợ iterator + sentinel và iterator + length, thậm chí có thể cả iterator + sentinel + length) và tạo một nhịp typedef đơn giản. Bởi vì, bạn biết đấy, đó là chung chung hơn.
Ded repeatator

3
@Ded repeatator: Phạm vi đang đến với C ++, nhưng đề xuất hiện tại (của Eric Niebler) yêu cầu hỗ trợ cho các khái niệm. Vì vậy, không phải trước C ++ 20.
einpoklum

8
@ HảiPhạmLê: Mảng không phân rã ngay lập tức thành con trỏ. hãy thử làm std::cout << sizeof(buffer) << '\n'và bạn sẽ thấy bạn nhận được 100 sizeof (int).
einpoklum

4
@Jim std::arraylà một container, nó sở hữu các giá trị. spanlà không sở hữu
Caleth 4/12/18

3
@Jim: std::arraylà một con thú hoàn toàn khác. Độ dài của nó được cố định tại thời gian biên dịch và đó là loại giá trị thay vì loại tham chiếu, như Caleth giải thích.
einpoklum

0

@einpoklum làm một công việc tốt đẹp của việc giới thiệu những gì một spantrong câu trả lời của mình ở đây . Tuy nhiên, ngay cả sau khi đọc câu trả lời của anh ấy, thật dễ dàng để một người mới bắt đầu vẫn có một chuỗi các câu hỏi suy nghĩ không được trả lời đầy đủ, chẳng hạn như sau:

  1. Làm thế nào spankhác với một mảng C? Tại sao không chỉ sử dụng một trong những? Có vẻ như đó chỉ là một trong những người có kích thước được biết đến ...
  2. Đợi đã, nghe có vẻ như vậy std::array, spankhác với nó như thế nào?
  3. Ồ, điều đó nhắc nhở tôi, không phải là std::vectorgiống như std::arrayvậy sao?
  4. Tôi thấy bối rối. :( Cái gì span?

Vì vậy, đây là một số rõ ràng bổ sung về điều đó:

NHỮNG CÂU HỎI TRỰC TIẾP CỦA TRẢ LỜI CỦA TÔI - VỚI CÁC THÊM CỦA TÔI TRONG BÓNG :

Nó là gì?

A span<T>là:

  • Một sự trừu tượng rất nhẹ của một chuỗi các giá trị liên tục Tở đâu đó trong bộ nhớ.
  • Về cơ bản một cấu trúc duy nhất{ T * ptr; std::size_t length; } với một loạt các phương pháp thuận tiện. (Lưu ý rằng điều này khác biệt rõ rệt std::array<>vì một spanphương thức truy cập tiện lợi, có thể so sánh với std::array, thông qua một con trỏ để nhậpT và độ dài (số phần tử) của loại T, trong khi đó std::arraylà một thùng chứa thực tế chứa một hoặc nhiều giá trị loại T.)
  • Loại không sở hữu (nghĩa là "loại tham chiếu" thay vì "loại giá trị"): Nó không bao giờ phân bổ cũng không giải quyết bất cứ điều gì và không giữ cho con trỏ thông minh tồn tại.

Nó trước đây được gọi là một array_viewvà thậm chí sớm hơn như array_ref.

Những phần táo bạo đó rất quan trọng đối với sự hiểu biết của một người, vì vậy đừng bỏ lỡ hoặc đọc sai chúng! A spanKHÔNG phải là một mảng C của các cấu trúc, cũng không phải là một cấu trúc của một mảng loại C Tcộng với chiều dài của mảng (về cơ bản nó sẽ là các std::array thùng chứa ), NOR là một mảng C của các con trỏ để nhập Tcộng với độ dài, nhưng đúng hơn là một cấu trúc đơn có chứa một con trỏT duy nhất để gõđộ dài , là số phần tử (loại T) trong khối bộ nhớ liền kề mà con trỏ cần nhập T! Theo cách này, chi phí duy nhất bạn đã thêm bằng cách sử dụngspanlà các biến để lưu trữ con trỏ và độ dài và bất kỳ hàm truy cập tiện lợi nào bạn sử dụng mà spancung cấp.

Đây là Không giống như một std::array<>std::array<>thực sự cấp phát bộ nhớ cho toàn bộ khối liền kề, và nó không giống như std::vector<>bởi vì một std::vectorlà về cơ bản chỉ là một std::arraymà cũng không năng động ngày càng tăng (thường là tăng gấp đôi kích thước) mỗi lần nó đầy lên và bạn cố gắng thêm cái gì khác với nó . Một std::arraylà cố định về kích thước, và một spanthậm chí không quản lý bộ nhớ của khối nó trỏ đến, nó chỉ trỏ tới khối nhớ, biết bao lâu khối bộ nhớ là, hiểu biết những gì kiểu dữ liệu là một trong C-mảng trong bộ nhớ và cung cấp các hàm truy cập tiện lợi để làm việc với các phần tử trong bộ nhớ liền kề đó .

một phần của tiêu chuẩn C ++:

std::spanlà một phần của tiêu chuẩn C ++ kể từ C ++ 20. Bạn có thể đọc tài liệu của nó ở đây: https://en.cppreference.com/w/cpp/container/span . Để xem cách sử dụng Google absl::Span<T>(array, length)trong C ++ 11 trở lên ngay hôm nay , hãy xem bên dưới.

Tóm tắt Mô tả và Tài liệu tham khảo chính:

  1. std::span<T, Extent>( Extent= "số phần tử trong chuỗi hoặc std::dynamic_extentnếu động". Một khoảng chỉ trỏ vào bộ nhớ và giúp bạn dễ dàng truy cập, nhưng KHÔNG quản lý nó!):
    1. https://en.cppreference.com/w/cpp/container/span
  2. std::array<T, N>(chú ý rằng nó có kích thước cố địnhN !):
    1. https://en.cppreference.com/w/cpp/container/array
    2. http://www.cplusplus.com/reference/array/array/
  3. std::vector<T> (tự động tăng kích thước khi cần thiết):
    1. https://en.cppreference.com/w/cpp/container/vector
    2. http://www.cplusplus.com/reference/vector/vector/

Làm thế nào tôi có thể sử dụng spantrong C ++ 11 trở lên ngày hôm nay ?

Google đã mở nguồn thư viện C ++ 11 nội bộ của họ dưới dạng thư viện "Abseil" của họ. Thư viện này nhằm cung cấp C ++ 14 đến C ++ 20 và hơn thế nữa là các tính năng hoạt động trong C ++ 11 trở lên, để bạn có thể sử dụng các tính năng của ngày mai, ngay hôm nay. Họ nói:

Khả năng tương thích với Tiêu chuẩn C ++

Google đã phát triển nhiều khái niệm trừu tượng phù hợp hoặc kết hợp chặt chẽ các tính năng được tích hợp vào C ++ 14, C ++ 17 và hơn thế nữa. Sử dụng các phiên bản Abseil của các bản tóm tắt này cho phép bạn truy cập các tính năng này ngay bây giờ, ngay cả khi mã của bạn chưa sẵn sàng cho cuộc sống trong một thế giới bài C ++ 11.

Dưới đây là một số tài nguyên và liên kết chính:

  1. Trang web chính: https://abseil.io/
  2. https://abseil.io/docs/cpp/
  3. Kho lưu trữ GitHub: https://github.com/abseil/abseil-cpp
  4. span.htiêu đề và absl::Span<T>(array, length)lớp mẫu: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189
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.