"ระยะเวลา" คืออะไรและฉันควรใช้เมื่อใด


237

เมื่อเร็ว ๆ นี้ฉันได้รับคำแนะนำให้ใช้span<T>ในโค้ดของฉันหรือเคยเห็นคำตอบบางอย่างในเว็บไซต์ซึ่งใช้spanคอนเทนเนอร์บางชนิด แต่ - ฉันไม่พบอะไรอย่างนั้นในไลบรารีมาตรฐาน C ++ 17

ดังนั้นสิ่งนี้ลึกลับspan<T>และทำไม (หรือเมื่อ) มันเป็นความคิดที่ดีที่จะใช้มันถ้ามันไม่ได้มาตรฐาน?


std::spanถูกเสนอในปี 2560 ใช้กับ C ++ 17 หรือ C ++ 20 ยังเห็นP0122R5 ช่วง: มุมมองขอบเขตที่ปลอดภัยสำหรับลำดับของวัตถุ คุณต้องการกำหนดเป้าหมายภาษานั้นจริงๆหรือ จะเป็นปีก่อนคอมไพเลอร์จะทัน
jww

6
@jww: ช่วงของค่อนข้างใช้งานได้กับ C ++ 11 ... เป็นมากกว่าgsl::span std::spanดูคำตอบของฉันด้านล่าง
einpoklum

บันทึกไว้ใน cppreference.com ด้วย: en.cppreference.com/w/cpp/container/span
Keith Thompson

1
@ KeithThompson: ไม่ใช่ในปี 2560 มันไม่ใช่ ...
einpoklum

@jww คอมไพเลอร์ทั้งหมดสนับสนุน std :: span <> ตอนนี้ในโหมด C ++ 20 และสามารถใช้ได้จาก libs บุคคลที่สามมากมาย คุณพูดถูก - เป็นปี: 2 ปีเพื่อความแม่นยำ
Contango

คำตอบ:


272

มันคืออะไร?

A span<T>คือ:

  • นามธรรมที่มีน้ำหนักเบามากของลำดับของค่าที่ต่อเนื่องชนิดTหนึ่งในหน่วยความจำ
  • โดยทั่วไปจะstruct { T * ptr; std::size_t length; }มีวิธีอำนวยความสะดวกมากมาย
  • ประเภทที่ไม่ได้เป็นเจ้าของ (เช่น"การอ้างอิงประเภท"แทนที่จะเป็น "ประเภทค่า"): มันไม่เคยจัดสรรหรือ deallocates อะไรและไม่ทำให้ตัวชี้สมาร์ทยังมีชีวิตอยู่

มันเคยเป็นที่รู้จักกันเป็นและแม้กระทั่งก่อนหน้านี้เป็นarray_viewarray_ref

ฉันควรใช้เมื่อใด

ก่อนอื่นไม่ควรใช้:

  • ไม่ได้ใช้มันในรหัสที่ก็อาจจะใช้คู่เริ่มต้นและสิ้นสุด iterators เช่นใดstd::sort, std::find_if, std::copyและทุกฟังก์ชั่นของผู้ที่ templated ซุปเปอร์ทั่วไป
  • อย่าใช้มันหากคุณมีคอนเทนเนอร์ไลบรารีมาตรฐาน (หรือบูสต์คอนเทนเนอร์เป็นต้น) ซึ่งคุณรู้ว่าเหมาะสมสำหรับรหัสของคุณ มันไม่ได้มีไว้เพื่อแทนที่พวกเขาใด ๆ

ตอนนี้เมื่อใช้จริง:

ใช้span<T>(ตามลำดับspan<const T>) แทนการยืนฟรีT*(ตามลำดับconst T*) ซึ่งคุณมีค่าความยาว ดังนั้นแทนที่ฟังก์ชั่นเช่น:

  void read_into(int* buffer, size_t buffer_size);

ด้วย:

  void read_into(span<int> buffer);

เหตุใดฉันจึงควรใช้ ทำไมเป็นสิ่งที่ดี

โอ้ช่วงเวลานั้นยอดเยี่ยมมาก! ใช้span...

  • หมายความว่าคุณสามารถทำงานร่วมกับตัวชี้ + ความยาว / เริ่ม + การรวมกันของตัวชี้เช่นเดียวกับที่คุณทำกับภาชนะไลบรารีมาตรฐานที่เป็นไปได้และแปลกใจเช่น:

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

    ... แต่ด้วยคลาสของภาชนะส่วนใหญ่ที่ไม่มีค่าใช้จ่าย

  • ให้คอมไพเลอร์ทำงานได้มากขึ้นสำหรับคุณในบางครั้ง ตัวอย่างเช่นสิ่งนี้:

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

    กลายเป็นสิ่งนี้:

    int buffer[BUFFER_SIZE];
    read_into(buffer);
    

    ... ซึ่งจะทำสิ่งที่คุณต้องการให้ทำ ดูเพิ่มเติมแนวทาง P.5

  • เป็นทางเลือกที่สมเหตุสมผลในการส่งผ่านconst vector<T>&ไปยังฟังก์ชั่นเมื่อคุณคาดว่าข้อมูลของคุณจะต่อเนื่องกันในหน่วยความจำ ไม่ต้องตะลึงอีกต่อไปด้วยกูรู C ++ ระดับสูงและยิ่งใหญ่!

  • อำนวยความสะดวกในการวิเคราะห์แบบคงที่ดังนั้นคอมไพเลอร์อาจช่วยให้คุณจับข้อบกพร่องที่โง่
  • อนุญาตให้ใช้เครื่องมือการตรวจแก้จุดบกพร่องการรวบรวมสำหรับการตรวจสอบขอบเขตรันไทม์ (เช่นspanวิธีการของจะมีรหัสการตรวจสอบขอบเขตภายใน#ifndef NDEBUG... #endif)
  • ระบุว่ารหัสของคุณ (ที่ใช้การขยาย) ไม่ได้เป็นเจ้าของหน่วยความจำที่แหลม

มีแรงจูงใจในการใช้spans มากขึ้นซึ่งคุณสามารถหาได้ในแนวทางหลักของC ++ - แต่คุณก็ไม่ทันรู้ตัว

เหตุใดจึงไม่อยู่ในไลบรารีมาตรฐาน (ตั้งแต่ C ++ 17)

มันอยู่ในไลบรารีมาตรฐาน - แต่เฉพาะ C ++ 20 เหตุผลก็คือมันยังค่อนข้างใหม่ในรูปแบบปัจจุบันร่วมกับโครงการแนวทางหลัก C ++ซึ่งเริ่มมีรูปแบบมาตั้งแต่ปี 2558 (แม้ว่าผู้แสดงความคิดเห็นจะชี้ให้เห็นว่ามันมีประวัติก่อนหน้านี้)

ดังนั้นฉันจะใช้มันยังไงถ้ามันยังไม่อยู่ในไลบรารี่มาตรฐาน?

มันเป็นส่วนหนึ่งของห้องสมุดสนับสนุนหลักของแนวทาง (GSL) การใช้งาน:

  • GSLของ Microsoft / Neil Macintosh มีการใช้งานแบบสแตนด์อโลน:gsl/span
  • GSL-Liteเป็นการนำส่วนหัวเดียวของทั้ง GSL (มันไม่ว่าใหญ่ไม่ต้องกังวล) span<T>รวมทั้ง

การใช้งาน GSL โดยทั่วไปถือว่าเป็นแพลตฟอร์มที่ใช้การสนับสนุน C ++ 14 [ 14 ] การใช้งานส่วนหัวทางเลือกเดียวเหล่านี้ไม่ได้ขึ้นอยู่กับเครื่องมืออำนวยความสะดวกของ GSL:

โปรดทราบว่าการใช้งานการขยายที่แตกต่างกันเหล่านี้มีความแตกต่างบางอย่างในวิธีการ / ฟังก์ชั่นสนับสนุนที่มาพร้อม และอาจแตกต่างจากเวอร์ชั่นที่ใช้ในไลบรารี่มาตรฐาน C ++ 20


อ่านเพิ่มเติม:คุณสามารถค้นหารายละเอียดทั้งหมดและข้อควรพิจารณาการออกแบบในข้อเสนออย่างเป็นทางการสุดท้ายก่อน C ++ 17, P0122R7: span: มุมมองที่ปลอดภัยสำหรับขอบเขตของลำดับวัตถุโดย Neal Macintosh และ Stephan J. Lavavej มันค่อนข้างยาวไปหน่อย นอกจากนี้ใน C ++ 20 ความหมายการเปรียบเทียบการเปรียบเทียบก็เปลี่ยนไป (ตามบทความสั้น ๆ นี้โดย Tony van Eerd)


2
มันจะสมเหตุสมผลมากกว่าที่จะสร้างมาตรฐานของช่วงทั่วไป (รองรับ iterator + sentinel และ iterator + length, หรือแม้แต่ iterator + sentinel + length) และทำให้พิมพ์ typedef ง่าย ๆ เพราะคุณรู้ว่ามันเป็นเรื่องธรรมดามากกว่า
Deduplicator

3
@Dupuplicator: ช่วงกำลังมาถึง C ++ แต่ข้อเสนอปัจจุบัน (โดย Eric Niebler) ต้องการการสนับสนุนแนวคิด ดังนั้นไม่ใช่ก่อน C ++ 20
einpoklum

8
@ HảiPhạmLê: อาร์เรย์ไม่สลายตัวทันทีเป็นพอยน์เตอร์ ลองทำstd::cout << sizeof(buffer) << '\n'แล้วคุณจะเห็นว่าคุณได้ 100 sizeof (int)
einpoklum

4
@Jim std::arrayเป็นคอนเทนเนอร์มันเป็นเจ้าของค่า spanไม่มีเจ้าของ
Caleth

3
@Jim: std::arrayเป็นสัตว์ที่แตกต่างอย่างสิ้นเชิง ความยาวของมันได้รับการแก้ไขในเวลาคอมไพล์และเป็นประเภทค่าแทนที่จะเป็นประเภทอ้างอิงตามที่ Caleth อธิบาย
einpoklum

1

@einpoklum ไม่ได้งานที่ดีงามของการแนะนำสิ่งที่spanเป็นคำตอบของเขาที่นี่ อย่างไรก็ตามแม้หลังจากอ่านคำตอบของเขาแล้วยังเป็นเรื่องง่ายสำหรับคนใหม่ที่ยังมีช่วงเวลาที่ยังคงมีคำถามแบบสตรีมที่คิดซึ่งยังไม่ได้รับคำตอบอย่างเต็มที่เช่น:

  1. เป็นวิธีspanที่แตกต่างจากอาร์เรย์ C? ทำไมไม่ใช้เพียงหนึ่งในนั้น? ดูเหมือนว่ามันเป็นเพียงหนึ่งในบรรดาขนาดที่รู้จักกันดี ...
  2. เดี๋ยวก่อนมันฟังstd::arrayแล้วจะspanแตกต่างอย่างไร?
  3. โอ้นั่นทำให้ฉันนึกไม่ออกstd::vectorเหมือนstd::arrayกันเหรอ?
  4. ผมงงไปหมดแล้ว. :( อะไรนะspan?

ดังนั้นนี่คือความชัดเจนเพิ่มเติมเกี่ยวกับที่:

อ้างโดยตรงจากคำตอบของเขา - ด้วยส่วนเพิ่มเติมของฉันใน BOLD :

มันคืออะไร?

A span<T>คือ:

  • นามธรรมที่มีน้ำหนักเบามากของลำดับของค่าที่ต่อเนื่องชนิดTหนึ่งในหน่วยความจำ
  • โดยทั่วไปเดียว struct { T * ptr; std::size_t length; }กับพวงของวิธีการความสะดวกสบาย (โปรดสังเกตว่าสิ่งนี้แตกต่างอย่างชัดเจนstd::array<>เพราะspanช่วยให้สะดวกในการเข้าถึงวิธีเปรียบเทียบได้std::arrayผ่านตัวชี้ไปยังประเภทTและความยาว (จำนวนองค์ประกอบ) ของประเภทTในขณะที่std::arrayเป็นภาชนะจริงที่เก็บค่าประเภทหนึ่งค่าหรือมากกว่าT)
  • ประเภทที่ไม่ได้เป็นเจ้าของ (เช่น"การอ้างอิงประเภท"แทนที่จะเป็น "ประเภทค่า"): มันไม่เคยจัดสรรหรือ deallocates อะไรและไม่ทำให้ตัวชี้สมาร์ทยังมีชีวิตอยู่

มันเคยเป็นที่รู้จักกันเป็นและแม้กระทั่งก่อนหน้านี้เป็นarray_viewarray_ref

ชิ้นส่วนที่หนาเหล่านี้มีความสำคัญต่อการทำความเข้าใจดังนั้นอย่าพลาดหรือเข้าใจผิด! A spanไม่ใช่ C-array ของ structs และไม่เป็น struct ของ C-array ชนิดTบวกความยาวของอาร์เรย์ (นี่จะเป็นสิ่งที่std::array ภาชนะบรรจุเป็นหลัก), NOR คือ C-array ของ structs ของตัวชี้ เพื่อพิมพ์Tบวกความยาว แต่แทนที่จะเป็นโครงสร้างเดียวที่มีตัวชี้Tเดียวให้พิมพ์และความยาวซึ่งเป็นจำนวนองค์ประกอบ (ชนิดT) ในบล็อกหน่วยความจำต่อเนื่องที่ตัวชี้พิมพ์Tชี้ไปที่! ด้วยวิธีนี้ค่าใช้จ่ายเพียงอย่างเดียวที่คุณเพิ่มโดยใช้spanเป็นตัวแปรในการจัดเก็บตัวชี้และระยะเวลาและความสะดวกสบายใด ๆ เข้าถึงฟังก์ชั่นที่คุณใช้ที่spanให้

นี่คือ UNLIKE a std::array<>เพราะstd::array<>จริง ๆ แล้วจัดสรรหน่วยความจำสำหรับบล็อกที่ต่อเนื่องกันทั้งหมดและ UNLIKE std::vector<>เพราะ a std::vectorนั้นเป็นเพียงแค่std::arrayว่ามันยังมีการเติบโตแบบไดนามิก (โดยปกติจะเพิ่มเป็นสองเท่าในแต่ละครั้ง) . A std::arrayได้รับการแก้ไขในขนาดและa spanไม่ได้จัดการหน่วยความจำของบล็อกมันชี้ไปที่มันเพียงชี้ไปที่บล็อกของหน่วยความจำรู้ระยะเวลาที่บล็อกของหน่วยความจำรู้ประเภทข้อมูลที่อยู่ใน C-array ในความทรงจำและให้ความสะดวกสบายเข้าถึงฟังก์ชั่นการทำงานที่มีองค์ประกอบในหน่วยความจำที่ต่อเนื่องกันว่า

มันเป็นส่วนหนึ่งของมาตรฐาน C ++:

std::spanเป็นส่วนหนึ่งของมาตรฐาน C ++ ณ C ++ 20 คุณสามารถอ่านเอกสารที่นี่: https://en.cppreference.com/w/cpp/container/span หากต้องการดูวิธีใช้ Google absl::Span<T>(array, length)ใน C ++ 11 หรือใหม่กว่าในวันนี้ดูด้านล่าง

คำอธิบายอย่างย่อและการอ้างอิงหลัก:

  1. std::span<T, Extent>( Extent= "จำนวนองค์ประกอบในลำดับหรือstd::dynamic_extentถ้าเป็นไดนามิก" ระยะห่างเพียงชี้ไปที่หน่วยความจำและทำให้เข้าถึงได้ง่าย แต่ไม่ได้จัดการ!):
    1. https://en.cppreference.com/w/cpp/container/span
  2. std::array<T, N>(สังเกตว่ามันมีขนาดคงที่N !):
    1. https://en.cppreference.com/w/cpp/container/array
    2. http://www.cplusplus.com/reference/array/array/
  3. std::vector<T> (ขยายขนาดแบบไดนามิกโดยอัตโนมัติตามความจำเป็น):
    1. https://en.cppreference.com/w/cpp/container/vector
    2. http://www.cplusplus.com/reference/vector/vector/

ฉันจะใช้spanใน C ++ 11 หรือหลังจากนั้นในวันนี้ ?

Google ได้เปิดห้องสมุด C ++ 11 ภายในของพวกเขาในรูปแบบของห้องสมุด "Abseil" ไลบรารี่นี้มีวัตถุประสงค์เพื่อจัดเตรียม C ++ 14 ถึง C ++ 20 และเกินคุณสมบัติที่ทำงานใน C ++ 11 และใหม่กว่าเพื่อให้คุณสามารถใช้ฟีเจอร์ของวันพรุ่งนี้ได้ในวันนี้ พวกเขาพูดว่า:

ความเข้ากันได้กับมาตรฐาน C ++

Google ได้พัฒนาบทคัดย่อมากมายที่จับคู่หรือจับคู่คุณสมบัติที่รวมอยู่ใน C ++ 14, C ++ 17 และอื่น ๆ การใช้บทคัดย่อ Abseil ของ abstractions เหล่านี้ช่วยให้คุณสามารถเข้าถึงฟีเจอร์เหล่านี้ได้ในขณะนี้แม้ว่ารหัสของคุณจะยังไม่พร้อมสำหรับการใช้ชีวิตในโลกโพสต์ C ++ 11

นี่คือแหล่งข้อมูลและลิงค์สำคัญ ๆ :

  1. เว็บไซต์หลัก: https://abseil.io/
  2. https://abseil.io/docs/cpp/
  3. ที่เก็บ GitHub: https://github.com/abseil/abseil-cpp
  4. span.habsl::Span<T>(array, length)คลาสส่วนหัวและเทมเพลต: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189

1
ฉันคิดว่าคุณนำข้อมูลที่สำคัญและมีประโยชน์มาขอบคุณ!
Gui Lima
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.