Apa itu "rentang" dan kapan saya harus menggunakannya?

237

Baru - baru ini saya mendapatkan saran untuk menggunakan span<T>kode saya, atau telah melihat beberapa jawaban di sini di situs yang menggunakan span- seharusnya semacam wadah. Tapi - saya tidak dapat menemukan hal seperti itu di perpustakaan standar C ++ 17.

Jadi apa yang misterius ini span<T>, dan mengapa (atau kapan) itu ide yang baik untuk menggunakannya jika itu tidak standar?

einpoklum
sumber
std::spandiusulkan pada 2017. Ini berlaku untuk C ++ 17 atau C ++ 20. Juga lihat P0122R5, span: bounds-safe views untuk urutan objek . Apakah Anda benar-benar ingin menargetkan bahasa itu? Ini akan menjadi tahun sebelum kompiler mengejar ketinggalan.
jww
6
@jww: span cukup dapat digunakan dengan C ++ 11 ... gsl::spandaripada std::span. Lihat juga jawaban saya di bawah ini.
einpoklum
Juga didokumentasikan di cppreference.com: en.cppreference.com/w/cpp/container/span
Keith Thompson
1
@KeithThompson: Bukan pada 2017 itu bukan ...
einpoklum
@jww Semua kompiler mendukung std :: span <> sekarang dalam mode C ++ 20. Dan rentang tersedia dari banyak lib pihak ke-3. Anda benar - itu bertahun-tahun: tepatnya 2 tahun.
Contango

Jawaban:

272

Apa itu?

A span<T>adalah:

  • Abstraksi yang sangat ringan dari urutan nilai-nilai tipe yang berdekatan Tdi memori.
  • Pada dasarnya struct { T * ptr; std::size_t length; }dengan banyak metode kenyamanan.
  • Tipe yang tidak memiliki (yaitu "tipe referensi" dan bukan "tipe nilai"): Tipe ini tidak pernah mengalokasikan atau membatalkan alokasi apa pun dan tidak membuat pointer pintar tetap hidup.

Sebelumnya dikenal sebagai array_viewdan bahkan sebelumnya sebagaiarray_ref .

Kapan saya harus menggunakannya?

Pertama, kapan tidak menggunakannya:

  • Jangan menggunakannya dalam kode yang hanya bisa mengambil setiap pasang dari awal & akhir iterator, seperti std::sort, std::find_if, std::copydan semua fungsi-fungsi templated super-generik.
  • Jangan menggunakannya jika Anda memiliki wadah perpustakaan standar (atau wadah Boost, dll.) Yang Anda tahu cocok untuk kode Anda. Itu tidak dimaksudkan untuk menggantikan mereka.

Sekarang kapan benar-benar menggunakannya:

Gunakan span<T>(masing-masing, span<const T>) alih-alih berdiri sendiri T*(masing-masing const T*) yang memiliki nilai panjang. Jadi, ganti fungsi seperti:

  void read_into(int* buffer, size_t buffer_size);

dengan:

  void read_into(span<int> buffer);

Mengapa saya harus menggunakannya? Mengapa ini hal yang baik?

Oh, bentang luar biasa! Menggunakan span...

  • berarti bahwa Anda dapat bekerja dengan kombinasi pointer + length / start + end pointer seperti yang Anda lakukan dengan kontainer perpustakaan standar yang mewah, pimped-out, misalnya:

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

    ... tetapi sama sekali tidak ada biaya overhead dari sebagian besar kelas kontainer.

  • memungkinkan kompiler melakukan lebih banyak pekerjaan untuk Anda kadang-kadang. Sebagai contoh, ini:

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

    menjadi ini:

    int buffer[BUFFER_SIZE];
    read_into(buffer);

    ... yang akan melakukan apa yang Anda inginkan. Lihat juga Pedoman H.5 .

  • adalah alternatif yang masuk akal untuk beralih const vector<T>&ke fungsi saat Anda berharap data Anda bersebelahan dalam memori. Tidak ada lagi dimarahi oleh guru C ++ tinggi-dan-perkasa!

  • memfasilitasi analisis statis, sehingga kompiler mungkin dapat membantu Anda menangkap bug konyol.
  • memungkinkan untuk instrumentasi kompilasi-debug untuk memeriksa batas runtime (yaitu spanmetode akan memiliki beberapa kode pemeriksaan batas dalam #ifndef NDEBUG...#endif )
  • menunjukkan bahwa kode Anda (yang menggunakan rentang) tidak memiliki memori berujung runcing.

Bahkan ada lebih banyak motivasi untuk menggunakan spans, yang dapat Anda temukan di pedoman inti C ++ - tetapi Anda mengetahui maksudnya.

Mengapa tidak ada di perpustakaan standar (pada C ++ 17)?

Itu ada di perpustakaan standar - tetapi hanya pada C ++ 20. Alasannya adalah bahwa itu masih cukup baru dalam bentuk saat ini, disusun dalam hubungannya dengan pedoman inti C ++ proyek , yang baru mulai terbentuk sejak tahun 2015. (Meskipun sebagai komentator menunjukkan, itu memiliki sejarah sebelumnya.)

Jadi bagaimana cara menggunakannya jika belum ada di perpustakaan standar?

Ini bagian dari Perpustakaan Dukungan Pedoman Inti (GSL). Implementasi:

  • Microsoft / Neil Macintosh's GSL berisi implementasi mandiri:gsl/span
  • GSL-Lite adalah implementasi satu-header dari seluruh GSL (tidak terlalu besar, jangan khawatir), termasuk span<T>.

Implementasi GSL umumnya mengasumsikan platform yang mengimplementasikan dukungan C ++ 14 [ 14 ]. Implementasi single-header alternatif ini tidak bergantung pada fasilitas GSL:

Perhatikan bahwa implementasi rentang yang berbeda ini memiliki beberapa perbedaan dalam metode / fungsi pendukung apa yang mereka gunakan; dan mereka juga agak berbeda dari versi yang masuk ke library standar di C ++ 20.


Bacaan lebih lanjut: Anda dapat menemukan semua detail dan pertimbangan desain dalam proposal resmi terakhir sebelum C ++ 17, P0122R7: span: bounds-safe views untuk urutan objek oleh Neal Macintosh dan Stephan J. Lavavej. Tapi agak lama. Juga, di C ++ 20, semantik perbandingan rentang berubah (mengikuti makalah singkat ini oleh Tony van Eerd).

einpoklum
sumber
2
Akan lebih masuk akal untuk membakukan rentang umum (mendukung iterator + sentinel dan panjang iterator +, bahkan mungkin iterator + sentinel + panjang) dan membuat rentang typedef sederhana. Karena, Anda tahu, itu lebih umum.
Deduplicator
3
@Dupuplikator: Rentang datang ke C ++, tetapi proposal saat ini (oleh Eric Niebler) membutuhkan dukungan untuk Konsep. Jadi tidak sebelum C ++ 20.
einpoklum
8
@ HảiPhạmLê: Array tidak segera membusuk menjadi pointer. coba lakukan std::cout << sizeof(buffer) << '\n'dan Anda akan melihat Anda mendapatkan 100 sizeof (int).
einpoklum
4
@ Jim std::arrayadalah sebuah wadah, ia memiliki nilai. spantidak memiliki
Caleth
3
@ Jim: std::arrayadalah binatang yang sama sekali berbeda. Panjangnya ditetapkan pada waktu kompilasi dan itu adalah tipe nilai daripada tipe referensi, seperti yang dijelaskan Caleth.
einpoklum
1

@einpoklum melakukan pekerjaan yang cukup baik untuk memperkenalkan apa yang spanada dalam jawabannya di sini . Namun, bahkan setelah membaca jawabannya, mudah bagi orang baru untuk melakukan rentang untuk masih memiliki serangkaian pertanyaan yang tidak terjawab sepenuhnya, seperti berikut:

  1. Bagaimana spanperbedaan dari array C? Mengapa tidak menggunakan salah satunya saja? Sepertinya itu hanya salah satu dari mereka dengan ukuran yang dikenal juga ...
  2. Tunggu, itu terdengar seperti std::array, bagaimana spanbedanya dengan itu?
  3. Oh, itu mengingatkan saya, bukankah itu std::vectorseperti std::arrayjuga?
  4. Aku begitu bingung. :( Apa itu span?

Jadi, inilah beberapa kejelasan tambahan tentang itu:

KUTIPAN LANGSUNG DARI JAWABANNYA - DENGAN TAMBAHAN SAYA DALAM BOLD :

Apa itu?

A span<T>adalah:

  • Abstraksi yang sangat ringan dari urutan nilai-nilai tipe yang berdekatan Tdi memori.
  • Pada dasarnya sebuah struct tunggal{ T * ptr; std::size_t length; } dengan banyak metode kenyamanan. (Perhatikan ini jelas berbeda dari std::array<>karena spanmemungkinkan metode aksesor kenyamanan, sebanding dengan std::array, melalui pointer ke jenisT dan panjang (jumlah elemen) dari jenis T, sedangkan std::arraywadah aktual yang memegang satu atau lebih nilai - nilai jenisT .)
  • Tipe yang tidak memiliki (yaitu "tipe referensi" dan bukan "tipe nilai"): Tipe ini tidak pernah mengalokasikan atau membatalkan alokasi apa pun dan tidak membuat pointer pintar tetap hidup.

Sebelumnya dikenal sebagai array_viewdan bahkan sebelumnya sebagaiarray_ref .

Bagian yang berani itu penting untuk pemahaman seseorang, jadi jangan sampai ketinggalan atau salah baca! A spanBUKAN C-array struct, juga bukan merupakan struct dari C-array tipe Tplus panjang array (ini akan menjadi dasarnya std::array wadah ), NOR apakah itu bukan C-array struct dari pointer untuk mengetik Tplus panjang, tetapi lebih merupakan struktur tunggal yang berisi satu pointerT tunggal untuk mengetik , dan panjangnya , yang merupakan jumlah elemen (tipe T) di blok memori yang berdekatan dimana pointer untuk mengetik Tpoin ke! Dengan cara ini, satu-satunya overhead yang telah Anda tambahkan dengan menggunakan aspanadalah variabel untuk menyimpan pointer dan panjang, dan fungsi aksesor kenyamanan yang Anda gunakanspan menyediakan.

Ini adalah UNLIKE std::array<>karena std::array<>sebenarnya mengalokasikan memori untuk seluruh blok yang bersebelahan, dan itu UNLIKE std::vector<>karena a std::vectorpada dasarnya hanya std::arrayyang juga tumbuh dinamis (biasanya dua kali lipat) setiap kali mengisi dan Anda mencoba menambahkan sesuatu yang lain ke dalamnya . Sebuah std::arrayadalah tetap dalam ukuran, dan sebuah spanbahkan tidak mengelola memori dari blok itu menunjuk, itu hanya poin ke blok memori, yang tahu berapa lama blok memori, tahu apa tipe data dalam C-array dalam memori, dan memberikan kemudahan akses atau fungsi untuk bekerja dengan elemen-elemen dalam memori yang berdekatan .

Ini adalah bagian dari standar C ++:

std::spanadalah bagian dari standar C ++ pada C ++ 20. Anda dapat membaca dokumentasinya di sini: https://en.cppreference.com/w/cpp/container/span . Untuk melihat cara menggunakan Google absl::Span<T>(array, length)di C ++ 11 atau lebih baru hari ini , lihat di bawah.

Deskripsi Ringkasan, dan Referensi Kunci:

  1. std::span<T, Extent>( Extent= "jumlah elemen dalam urutan, atau std::dynamic_extentjika dinamis". Rentang hanya menunjuk ke memori dan membuatnya mudah diakses, tetapi TIDAK mengelolanya!):
    1. https://en.cppreference.com/w/cpp/container/span
  2. std::array<T, N>(perhatikan ukurannya tetapN !):
    1. https://en.cppreference.com/w/cpp/container/array
    2. http://www.cplusplus.com/reference/array/array/
  3. std::vector<T> (secara otomatis tumbuh dalam ukuran sesuai kebutuhan):
    1. https://en.cppreference.com/w/cpp/container/vector
    2. http://www.cplusplus.com/reference/vector/vector/

Bagaimana Saya Dapat Menggunakan spanC ++ 11 atau lebih baru hari ini ?

Google telah membuka sumber pustaka C ++ 11 internal mereka dalam bentuk pustaka "Abseil" mereka. Pustaka ini dimaksudkan untuk menyediakan C ++ 14 hingga C ++ 20 dan di luar fitur yang berfungsi di C ++ 11 dan yang lebih baru, sehingga Anda dapat menggunakan fitur besok, hari ini. Mereka bilang:

Kompatibilitas dengan Standar C ++

Google telah mengembangkan banyak abstraksi yang cocok atau cocok dengan fitur yang dimasukkan ke dalam C ++ 14, C ++ 17, dan seterusnya. Menggunakan versi Abseil dari abstraksi ini memungkinkan Anda untuk mengakses fitur-fitur ini sekarang, bahkan jika kode Anda belum siap untuk digunakan di dunia postingan C ++ 11.

Berikut beberapa sumber dan tautan utama:

  1. Situs utama: https://abseil.io/
  2. https://abseil.io/docs/cpp/
  3. Repositori GitHub: https://github.com/abseil/abseil-cpp
  4. span.hheader, dan absl::Span<T>(array, length)kelas templat: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189
Gabriel Staples
sumber
1
Saya pikir Anda memunculkan informasi penting dan berguna, terima kasih!
Gui Lima