วิธีทำให้ประเภทที่กำหนดเองของฉันทำงานกับ“ ช่วงตามลูป” ได้อย่างไร


252

เช่นเดียวกับหลาย ๆ คนในทุกวันนี้ฉันได้ลองใช้คุณสมบัติที่แตกต่างที่ C ++ 11 นำมาใช้ หนึ่งในรายการโปรดของฉันคือ "ช่วงตามลูป"

ฉันเข้าใจ:

for(Type& v : a) { ... }

เทียบเท่ากับ:

for(auto iv = begin(a); iv != end(a); ++iv)
{
  Type& v = *iv;
  ...
}

และนั่นbegin()ก็แค่ส่งคืนa.begin()สำหรับคอนเทนเนอร์มาตรฐาน

แต่สิ่งที่ถ้าผมต้องการที่จะทำให้ประเภทของฉันเอง "ช่วงที่ใช้สำหรับวง" -aware ?

ฉันควรจะมีความเชี่ยวชาญbegin()และend()?

หากประเภทที่กำหนดเองของฉันเป็นของเนมสเปซxmlฉันควรกำหนดxml::begin()หรือstd::begin()?

ในระยะสั้นสิ่งที่เป็นแนวทางในการทำเช่นนั้น?


มันเป็นไปได้อย่างใดอย่างหนึ่งโดยการกำหนดเป็นสมาชิกหรือเพื่อนแบบคงที่หรือฟรีbegin/end begin/endเพียงแค่ระวังให้เนมสเปซที่คุณใส่ฟังก์ชั่นฟรี: stackoverflow.com/questions/28242073/…
alfC

for( auto x : range<float>(0,TWO_PI, 0.1F) ) { ... }สามารถทุกคนกรุณาโพสต์คำตอบกับตัวอย่างของช่วงค่าลอยซึ่งเป็นภาชนะที่ไม่ได้ไปนี้: ฉันอยากรู้ว่าคุณทำงานอย่างไรกับข้อเท็จจริงที่ว่า `` ผู้ปฏิบัติการ '= () `` ยากที่จะนิยาม และสิ่งที่เกี่ยวกับ dereferencing ( *__begin) ในกรณีนี้? ฉันคิดว่ามันจะเป็นการช่วยเหลือที่ดีถ้ามีคนแสดงให้เราเห็นว่ามันทำอย่างไร!
BitTickler

คำตอบ:


183

มาตรฐานมีการเปลี่ยนแปลงตั้งแต่คำถาม (และคำตอบส่วนใหญ่) ถูกโพสต์ในการแก้ปัญหาของรายงานข้อบกพร่องนี้

วิธีที่จะทำให้for(:)ลูปทำงานกับประเภทของคุณXได้แล้วตอนนี้หนึ่งในสองวิธี:

  • สร้างสมาชิกX::begin()และX::end()ส่งคืนสิ่งที่ทำหน้าที่เหมือนตัววนซ้ำ

  • สร้างฟังก์ชั่นฟรีbegin(X&)และend(X&)ว่าสิ่งที่ผลตอบแทนที่ทำหน้าที่เหมือน iterator ใน namespace เดียวกันเป็นชนิดของคุณX

และคล้ายกันสำหรับconstรูปแบบต่างๆ สิ่งนี้จะทำงานทั้งในคอมไพเลอร์ที่ใช้การเปลี่ยนแปลงรายงานข้อบกพร่องและคอมไพเลอร์ที่ไม่มี

วัตถุที่ส่งคืนไม่จำเป็นต้องเป็นตัววนซ้ำจริง ๆ การfor(:)วนซ้ำซึ่งแตกต่างจากส่วนใหญ่ของมาตรฐาน C ++ ถูกระบุให้ขยายเป็นสิ่งที่เทียบเท่ากับ :

for( range_declaration : range_expression )

กลายเป็น:

{
  auto && __range = range_expression ;
  for (auto __begin = begin_expr,
            __end = end_expr;
            __begin != __end; ++__begin) {
    range_declaration = *__begin;
    loop_statement
  }
}

โดยที่ตัวแปรที่ขึ้นต้นด้วย__มีไว้สำหรับการแสดงออกเท่านั้นและbegin_exprและend_exprเป็นเวทย์มนตร์ที่เรียกใช้begin/ end

ข้อกำหนดเกี่ยวกับค่าส่งคืนเริ่มต้น / สิ้นสุดนั้นง่าย: คุณต้องโอเวอร์โหลดล่วงหน้า - ++ตรวจสอบให้แน่ใจว่านิพจน์การเริ่มต้นนั้นถูกต้องไบนารี!=ที่สามารถใช้ในบริบทบูลีน, ยูนารี*ที่ส่งคืนสิ่งที่คุณสามารถกำหนดค่าเริ่มต้นrange_declarationด้วย destructor

การทำเช่นนี้ในลักษณะที่ไม่เข้ากันได้กับตัววนซ้ำอาจเป็นความคิดที่ไม่ถูกต้องเนื่องจากการทำซ้ำในอนาคตของ C ++ อาจเป็นนักขี่ม้าที่ค่อนข้างจะทำลายรหัสของคุณหากคุณทำ

นอกจากนี้ยังมีเหตุผลที่การแก้ไขมาตรฐานในอนาคตจะอนุญาตend_exprให้ส่งคืนประเภทที่แตกต่างกันbegin_exprได้ สิ่งนี้มีประโยชน์ในการอนุญาตให้ประเมิน "lazy-end" (เช่นการตรวจจับการสิ้นสุดค่า null) ซึ่งง่ายต่อการปรับให้มีประสิทธิภาพเท่ากับการวนรอบ C ที่เขียนด้วยมือและข้อดีอื่น ๆ ที่คล้ายกัน


¹โปรดทราบว่าfor(:)ลูปจะเก็บชั่วคราวในauto&&ตัวแปรและส่งให้คุณในรูปแบบ lvalue คุณไม่สามารถตรวจพบว่าคุณทำซ้ำชั่วคราว (หรือ rvalue อื่น ๆ ); โอเวอร์โหลดดังกล่าวจะไม่ถูกเรียกโดยfor(:)ลูป ดู [stmt.ranged] 1.2-1.3 จาก n4527

²ทั้งโทรbegin/ endวิธีการหรือ ADL เท่านั้นค้นหาแบบของฟังก์ชั่นฟรีbegin/ end, หรือมายากลสำหรับการสนับสนุนอาร์เรย์แบบ C โปรดทราบstd::beginว่าไม่ได้เรียกว่าเว้นแต่จะrange_expressionส่งกลับวัตถุประเภทnamespace stdหรือขึ้นอยู่กับเดียวกัน


ใน มีการอัปเดตนิพจน์ช่วงสำหรับ

{
  auto && __range = range_expression ;
  auto __begin = begin_expr;
  auto __end = end_expr;
  for (;__begin != __end; ++__begin) {
    range_declaration = *__begin;
    loop_statement
  }
}

ด้วยประเภทของ__beginและ__endได้รับการแยกออก

สิ่งนี้อนุญาตให้ตัววนซ้ำสิ้นสุดไม่เหมือนกับชนิดเริ่มต้น ประเภทตัววนซ้ำสุดท้ายของคุณสามารถเป็น "sentinel" ซึ่งรองรับเฉพาะ!=กับตัววนซ้ำเริ่มต้นเท่านั้น

ตัวอย่างที่ใช้งานได้จริงว่าทำไมสิ่งนี้มีประโยชน์คือตัววนซ้ำสุดท้ายของคุณสามารถอ่าน "ตรวจสอบchar*เพื่อดูว่ามันชี้ไปที่'0'" เมื่อ==ใช้ a char*หรือไม่ สิ่งนี้อนุญาตให้มีการแสดงออกในช่วง C ++ เพื่อสร้างรหัสที่ดีที่สุดเมื่อทำการวนซ้ำผ่านchar*บัฟเฟอร์ที่สิ้นสุดด้วยค่า null

struct null_sentinal_t {
  template<class Rhs,
    std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
  >
  friend bool operator==(Rhs const& ptr, null_sentinal_t) {
    return !*ptr;
  }
  template<class Rhs,
    std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
  >
  friend bool operator!=(Rhs const& ptr, null_sentinal_t) {
    return !(ptr==null_sentinal_t{});
  }
  template<class Lhs,
    std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
  >
  friend bool operator==(null_sentinal_t, Lhs const& ptr) {
    return !*ptr;
  }
  template<class Lhs,
    std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
  >
  friend bool operator!=(null_sentinal_t, Lhs const& ptr) {
    return !(null_sentinal_t{}==ptr);
  }
  friend bool operator==(null_sentinal_t, null_sentinal_t) {
    return true;
  }
  friend bool operator!=(null_sentinal_t, null_sentinal_t) {
    return false;
  }
};

ตัวอย่างสดในคอมไพเลอร์โดยไม่มีการสนับสนุนแบบเต็ม C ++ 17; forขยายวงด้วยตนเอง


หากช่วงขึ้นอยู่กับการใช้กลไกการค้นหาที่แตกต่างกันก็อาจเป็นไปได้ที่จะจัดเรียงตามช่วงที่ได้รับคู่beginและendฟังก์ชั่นที่แตกต่างกว่าที่มีอยู่ในรหัสปกติ บางทีพวกเขาอาจมีความเชี่ยวชาญเฉพาะในการทำงานที่แตกต่างกัน (เช่นเร็วขึ้นโดยไม่สนใจอาร์กิวเมนต์ท้ายสุดเพื่อให้ได้ประสิทธิภาพสูงสุดที่เป็นไปได้) แต่ฉันไม่ดีพอที่จะใช้ namespaces เพื่อให้แน่ใจว่าจะทำอย่างไร
Aaron McDaid

@AaronMcDaid ไม่จริงมาก คุณจะพบผลลัพธ์ที่น่าประหลาดใจได้อย่างง่ายดายเพราะวิธีการโทรเริ่มต้น / สิ้นสุดบางรายการจะจบลงด้วยช่วงตามสำหรับการเริ่มต้น / สิ้นสุดและอื่น ๆ จะไม่ การเปลี่ยนแปลงที่ไม่มีพิษภัย (จากฝั่งไคลเอ็นต์) จะทำให้เกิดการเปลี่ยนแปลงพฤติกรรม
Yakk - Adam Nevraumont

1
begin(X&&)คุณไม่จำเป็นต้อง ชั่วคราวจะถูกระงับในกลางอากาศโดยauto&&อยู่ในช่วงสำหรับและbeginจะถูกเรียกด้วย lvalue ( __range) เสมอ
TC

2
คำตอบนี้จะได้ประโยชน์จากตัวอย่างเทมเพลตที่สามารถคัดลอกและนำไปใช้ได้จริง
Tomáš Zato - Reinstate Monica

ฉันอยากจะเน้นเรื่องคุณสมบัติของตัววนซ้ำ (*, ++,! =) ฉันควรขอให้คุณใช้ถ้อยคำนี้ซ้ำเพื่อให้รายละเอียดของตัววนซ้ำประเภทนั้นโดดเด่นยิ่งขึ้น
Red.Wave

62

ฉันเขียนคำตอบของฉันเพราะบางคนอาจจะมีความสุขมากกว่ากับตัวอย่างชีวิตจริงที่เรียบง่ายโดยไม่มี STL

ฉันมีการใช้งาน data array แบบเรียบง่ายของตัวเองด้วยเหตุผลบางอย่างและฉันต้องการใช้ช่วงที่เป็นลูป นี่คือทางออกของฉัน:

 template <typename DataType>
 class PodArray {
 public:
   class iterator {
   public:
     iterator(DataType * ptr): ptr(ptr){}
     iterator operator++() { ++ptr; return *this; }
     bool operator!=(const iterator & other) const { return ptr != other.ptr; }
     const DataType& operator*() const { return *ptr; }
   private:
     DataType* ptr;
   };
 private:
   unsigned len;
   DataType *val;
 public:
   iterator begin() const { return iterator(val); }
   iterator end() const { return iterator(val + len); }

   // rest of the container definition not related to the question ...
 };

จากนั้นตัวอย่างการใช้งาน:

PodArray<char> array;
// fill up array in some way
for(auto& c : array)
  printf("char: %c\n", c);

2
ตัวอย่างมีเมธอด start () และ end () และยังมีคลาส iterator ตัวอย่างพื้นฐาน (เข้าใจง่าย) ที่สามารถปรับเปลี่ยนได้ง่ายสำหรับคอนเทนเนอร์ประเภทใด ๆ ที่กำหนดเอง การเปรียบเทียบ std :: array <> และการใช้งานทางเลือกอื่น ๆ ที่เป็นไปได้นั้นเป็นคำถามที่แตกต่างกันและในความคิดของฉันไม่มีส่วนเกี่ยวข้องกับช่วงของลูป
csjpeter

นี่คือคำตอบที่รัดกุมและเป็นประโยชน์! มันเป็นสิ่งที่ฉันกำลังมองหา! ขอบคุณ!
Zac Taylor

1
มันจะเหมาะสมมากกว่าที่จะเอาconst คัดเลือกตอบแทนสำหรับการconst DataType& operator*()และให้ผู้ใช้เลือกที่จะใช้const auto&หรือauto&? ขอบคุณครับคำตอบที่ดี;)
ริก

53

ส่วนที่เกี่ยวข้องของมาตรฐานคือ 6.5.4 / 1:

ถ้า _RangeT เป็นประเภทคลาส unquali fi ed-ids เริ่มต้นและสิ้นสุดจะค้นหาในขอบเขตของคลาส _RangeT ราวกับว่าโดยการค้นหาการเข้าถึงการเข้าถึงสมาชิกระดับสมาชิก (3.4.5) และถ้าอย่างใดอย่างหนึ่ง (หรือทั้งสอง) declar nds ประกาศอย่างน้อยหนึ่งเริ่ม - expr และ end-expr คือ__range.begin()และ__range.end()ตามลำดับ

- มิฉะนั้น start-expr และ end-expr คือ begin(__range)และ end(__range)ตามลำดับโดยที่เริ่มต้นและสิ้นสุดถูกค้นหาด้วยการค้นหาอาร์กิวเมนต์ (3.4.2) สำหรับวัตถุประสงค์ของการค้นหาชื่อเนมสเปซ std คือเนมสเปซที่เชื่อมโยง

ดังนั้นคุณสามารถทำสิ่งใดสิ่งหนึ่งต่อไปนี้:

  • กำหนด beginและendฟังก์ชั่นสมาชิก
  • กำหนด beginและendฟังก์ชั่นอิสระที่จะพบได้โดย ADL (เวอร์ชั่นที่ง่าย: ใส่ไว้ในเนมสเปซเดียวกับคลาส)
  • เชี่ยวชาญstd::beginและstd::end

std::beginเรียกbegin()ฟังก์ชันสมาชิกแล้วดังนั้นหากคุณใช้เพียงอย่างใดอย่างหนึ่งข้างต้นผลลัพธ์ก็ควรจะเหมือนกันไม่ว่าคุณจะเลือกแบบใด นั่นเป็นผลลัพธ์ที่เหมือนกันสำหรับ ranged-based สำหรับลูปและผลลัพธ์เดียวกันสำหรับโค้ดมรรตัยเพียงอย่างเดียวที่ไม่มีกฎการแก้ไขชื่อเวทมนต์ของตัวเองดังนั้นเพียงแค่ทำusing std::begin;begin(a)ตามมาด้วยการโทรไปอย่างไม่มีเงื่อนไข

หากคุณใช้ฟังก์ชั่นสมาชิกและ ADL แล้วช่วงตามลูปควรเรียกฟังก์ชั่นสมาชิกในขณะที่ปุถุชนเพียงจะเรียกฟังก์ชั่น ADL ดีที่สุดให้แน่ใจว่าพวกเขาทำสิ่งเดียวกันในกรณีนั้น!

หากสิ่งที่คุณเขียนใช้ส่วนติดต่อคอนเทนเนอร์แล้วมันจะมีbegin()และend()ฟังก์ชั่นสมาชิกแล้วซึ่งควรจะเพียงพอ ถ้าเป็นช่วงที่ไม่ใช่คอนเทนเนอร์ (ซึ่งเป็นความคิดที่ดีถ้ามันไม่เปลี่ยนรูปหรือถ้าคุณไม่รู้ขนาดด้านหน้า) คุณมีอิสระที่จะเลือก

ตัวเลือกที่คุณ lay out ทราบว่าคุณต้องไม่std::begin()เกิน คุณได้รับอนุญาตให้เชี่ยวชาญแม่แบบมาตรฐานสำหรับประเภทที่ผู้ใช้กำหนด แต่นอกเหนือจากนั้นการเพิ่มคำจำกัดความในเนมสเปซ std เป็นพฤติกรรมที่ไม่ได้กำหนด แต่อย่างไรก็ตามฟังก์ชั่นมาตรฐานที่เชี่ยวชาญนั้นเป็นตัวเลือกที่ไม่ดีถ้าเพียงเพราะการขาดความเชี่ยวชาญบางส่วนของฟังก์ชั่นหมายความว่าคุณสามารถทำได้สำหรับคลาสเดียวเท่านั้นไม่ใช่สำหรับเทมเพลตคลาส


ไม่ได้มีข้อกำหนดบางอย่างที่ผู้ทำซ้ำตอบสนองมาก? เช่นเป็น ForwardIterator หรือบางสิ่งบางอย่างตามสายเหล่านั้น
Pubby

2
@Pubby: ดู 6.5.4 ฉันคิดว่า InputIterator เพียงพอแล้ว แต่จริงๆแล้วผมไม่คิดว่าชนิดกลับมีจะเป็น iterator ที่ทั้งหมดในช่วงที่ใช้สำหรับ คำสั่งที่กำหนดไว้ในมาตรฐานโดยสิ่งที่มันเทียบเท่ากับดังนั้นจึงพอที่จะใช้เพียงการแสดงออกที่ใช้ในรหัสในมาตรฐาน: ผู้ประกอบการ!=คำนำหน้าและเอก++ *มันอาจไม่ฉลาดในการใช้งานbegin()และend()ฟังก์ชั่นสมาชิกหรือฟังก์ชั่น ADL ที่ไม่ใช่สมาชิกที่ส่งคืนสิ่งอื่นนอกเหนือจากตัววนซ้ำ แต่ฉันคิดว่ามันถูกกฎหมาย ความเชี่ยวชาญstd::beginที่จะกลับไม่ใช่ iterator เป็น UB ผมคิดว่า
Steve Jessop

คุณแน่ใจหรือว่าไม่ต้องใช้เกินพิกัดมาตรฐาน :: เริ่มต้น? ฉันถามเพราะห้องสมุดมาตรฐานทำเช่นนั้นในบางกรณี
ThreeBit

@ThreeBit: ใช่ฉันแน่ใจ กฎสำหรับการใช้งานไลบรารีมาตรฐานแตกต่างจากกฎสำหรับโปรแกรม
Steve Jessop

3
ความต้องการนี้ได้รับการปรับปรุงสำหรับopen-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1442
TC

34

ฉันควรจะเป็นผู้เชี่ยวชาญในการเริ่มต้น () และสิ้นสุด () หรือไม่

เท่าที่ฉันรู้นั่นก็เพียงพอแล้ว คุณต้องให้แน่ใจว่าการเพิ่มตัวชี้จะได้รับตั้งแต่ต้นจนจบ

ตัวอย่างถัดไป (มันหายไปรุ่น const ของการเริ่มต้นและสิ้นสุด) รวบรวมและทำงานได้ดี

#include <iostream>
#include <algorithm>

int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }
    int * begin()
    {
        return &v[0];
    }
    int * end()
    {
        return &v[10];
    }

    int v[10];
};

int main()
{
    A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}

นี่คืออีกตัวอย่างหนึ่งที่มี start / end เป็น function พวกเขาต้องอยู่ในเนมสเปซเดียวกันกับคลาสเนื่องจาก ADL:

#include <iostream>
#include <algorithm>


namespace foo{
int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }

    int v[10];
};

int *begin( A &v )
{
    return &v.v[0];
}
int *end( A &v )
{
    return &v.v[10];
}
} // namespace foo

int main()
{
    foo::A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}

1
@ereOn ในเนมสเปซเดียวกันกับที่กำหนดคลาส ดูตัวอย่างที่ 2
BЈовић

2
ขอแสดงความยินดีด้วย :) คุณควรพูดถึงคำศัพท์ Argument Dependent Lookup (ADL) หรือ Koenig Lookup สำหรับตัวอย่างที่สอง (เพื่ออธิบายว่าทำไมฟังก์ชันฟรีควรอยู่ในเนมสเปซเดียวกันกับคลาสที่ทำงาน)
Matthieu M.

1
@ereOn: จริง ๆ แล้วคุณทำไม่ได้ ADL เกี่ยวกับการขยายขอบเขตเพื่อค้นหาเพื่อรวมเนมสเปซที่เป็นของอาร์กิวเมนต์โดยอัตโนมัติ มีบทความ ACCUที่ดีเกี่ยวกับการแก้ปัญหาการโอเวอร์โหลดซึ่งโชคไม่ดีที่ข้ามส่วนการค้นหาชื่อ การค้นหาชื่อเกี่ยวข้องกับการรวบรวมฟังก์ชันผู้สมัครคุณเริ่มต้นด้วยการดูในขอบเขตปัจจุบัน + ขอบเขตของอาร์กิวเมนต์ หากไม่พบชื่อที่ตรงกันคุณจะเลื่อนไปยังขอบเขตพาเรนต์ของขอบเขตปัจจุบันและค้นหาอีกครั้ง ... จนกว่าจะถึงขอบเขตทั่วโลก
Matthieu M.

1
@BЈовићขออภัยด้วยเหตุผลใดในฟังก์ชั่น end () คุณส่งคืนตัวชี้อันตรายหรือไม่? ฉันรู้ว่ามันใช้งานได้ แต่ฉันต้องการที่จะเข้าใจตรรกะของสิ่งนี้ จุดจบของอาร์เรย์คือ v [9] ทำไมคุณถึงกลับ v [10]
gedamial

1
@gedamial ฉันเห็นด้วย return v + 10ผมคิดว่ามันควรจะเป็น &v[10]การยกเลิกการระบุตำแหน่งหน่วยความจำเพิ่งผ่านอาร์เรย์
มิลลี่สมิ ธ

16

ในกรณีที่คุณต้องการสำรองการวนซ้ำของคลาสโดยตรงกับสมาชิกstd::vectorหรือstd::mapนี่คือรหัสสำหรับ:

#include <iostream>
using std::cout;
using std::endl;
#include <string>
using std::string;
#include <vector>
using std::vector;
#include <map>
using std::map;


/////////////////////////////////////////////////////
/// classes
/////////////////////////////////////////////////////

class VectorValues {
private:
    vector<int> v = vector<int>(10);

public:
    vector<int>::iterator begin(){
        return v.begin();
    }
    vector<int>::iterator end(){
        return v.end();
    }
    vector<int>::const_iterator begin() const {
        return v.begin();
    }
    vector<int>::const_iterator end() const {
        return v.end();
    }
};

class MapValues {
private:
    map<string,int> v;

public:
    map<string,int>::iterator begin(){
        return v.begin();
    }
    map<string,int>::iterator end(){
        return v.end();
    }
    map<string,int>::const_iterator begin() const {
        return v.begin();
    }
    map<string,int>::const_iterator end() const {
        return v.end();
    }

    const int& operator[](string key) const {
        return v.at(key);
    }
    int& operator[](string key) {
        return v[key];
    } 
};


/////////////////////////////////////////////////////
/// main
/////////////////////////////////////////////////////

int main() {
    // VectorValues
    VectorValues items;
    int i = 0;
    for(int& item : items) {
        item = i;
        i++;
    }
    for(int& item : items)
        cout << item << " ";
    cout << endl << endl;

    // MapValues
    MapValues m;
    m["a"] = 1;
    m["b"] = 2;
    m["c"] = 3;
    for(auto pair: m)
        cout << pair.first << " " << pair.second << endl;
}

2
มันมูลค่าการกล่าวขวัญว่าconst_iteratorยังสามารถเข้าถึงได้ในauto(C ++ 11) วิธีที่ใช้ได้กับระบบปฏิบัติการผ่านcbegin, cendฯลฯ
underscore_d

2

ที่นี่ฉันกำลังแบ่งปันตัวอย่างที่ง่ายที่สุดในการสร้างประเภทที่กำหนดเองซึ่งจะทำงานกับ " range-based for loop ":

#include<iostream>
using namespace std;

template<typename T, int sizeOfArray>
class MyCustomType
{
private:
    T *data;
    int indx;
public:
    MyCustomType(){
        data = new T[sizeOfArray];
        indx = -1;
    }
    ~MyCustomType(){
        delete []data;
    }
    void addData(T newVal){
        data[++indx] = newVal;
    }

    //write definition for begin() and end()
    //these two method will be used for "ranged based loop idiom"
    T* begin(){
        return &data[0];
    }
    T* end(){
        return  &data[sizeOfArray];
    }
};
int main()
{
    MyCustomType<double, 2> numberList;
    numberList.addData(20.25);
    numberList.addData(50.12);
    for(auto val: numberList){
        cout<<val<<endl;
    }
    return 0;
}

หวังว่ามันจะเป็นประโยชน์สำหรับนักพัฒนามือใหม่อย่างฉัน: p :)
ขอบคุณ


เหตุใดจึงไม่จัดสรรองค์ประกอบพิเศษหนึ่งรายการเพื่อหลีกเลี่ยงความจำที่ไม่ถูกต้องในวิธีการสิ้นสุดของคุณ
AndersK

@Anders เนื่องจากผู้ทำหน้าที่ทำซ้ำทั้งหมดเกือบทั้งหมดชี้ไปที่หลังจากสิ้นสุดโครงสร้างที่มี end()ฟังก์ชั่นของตัวเองอย่างเห็นได้ชัดไม่ dereference สถานที่หน่วยความจำที่ไม่เหมาะสมเพราะมันใช้เวลาเพียง 'ที่อยู่ของ' สถานที่ตั้งของหน่วยความจำนี้ การเพิ่มองค์ประกอบพิเศษจะหมายความว่าคุณต้องการหน่วยความจำเพิ่มขึ้นและการใช้your_iterator::end()วิธีใด ๆ ก็ตามที่ตรวจสอบว่าค่าดังกล่าวไม่สามารถทำงานร่วมกับตัววนซ้ำอื่น ๆ ได้อยู่ดีเพราะมันถูกสร้างขึ้นในลักษณะเดียวกัน
Qqwy

@Qqwy วิธีการสิ้นสุดของเขายกเลิกการอ้างอิง - return &data[sizeofarray]IMHO มันควรจะส่งคืนข้อมูลที่อยู่ + sizeofarray แต่สิ่งที่ฉันรู้
AndersK

@Anders คุณถูกต้อง ขอบคุณที่ทำให้ฉันคม :-) ใช่data + sizeofarrayจะเป็นวิธีที่ถูกต้องในการเขียนนี้
Qqwy

1

คำตอบของ Chris Redford ก็ใช้ได้กับตู้คอนเทนเนอร์ของ Qt (แน่นอน) นี่คือการปรับ (สังเกตว่าฉันคืน a constBegin()ตามลำดับconstEnd()จากวิธีการ const_iterator):

class MyCustomClass{
    QList<MyCustomDatatype> data_;
public:    
    // ctors,dtor, methods here...

    QList<MyCustomDatatype>::iterator begin() { return data_.begin(); }
    QList<MyCustomDatatype>::iterator end() { return data_.end(); }
    QList<MyCustomDatatype>::const_iterator begin() const{ return data_.constBegin(); }
    QList<MyCustomDatatype>::const_iterator end() const{ return data_.constEnd(); }
};

0

ฉันต้องการอธิบายบางส่วนของคำตอบของ @Steve Jessop ซึ่งตอนแรกฉันไม่เข้าใจ หวังว่ามันจะช่วย

std::beginเรียกbegin()ฟังก์ชันสมาชิกแล้วดังนั้นหากคุณใช้เพียงอย่างใดอย่างหนึ่งข้างต้นผลลัพธ์ก็ควรจะเหมือนกันไม่ว่าคุณจะเลือกแบบใด นั่นเป็นผลเหมือนกันสำหรับ ranged ที่ใช้สำหรับลูปและยังผลเดียวกันรหัสเพียงมนุษย์ที่ไม่ได้มีมนต์ขลังกฎความละเอียดชื่อของตัวเองดังนั้นก็ไม่ ตามมาด้วยการโทรไปอย่างไม่มีเงื่อนไขusing std::begin;begin(a)

หากคุณใช้ฟังก์ชั่นสมาชิก และ ฟังก์ชั่นADLแล้วช่วงตามลูปควรเรียกฟังก์ชั่นสมาชิกในขณะที่ปุถุชนเพียงจะเรียกฟังก์ชั่น ADL ดีที่สุดให้แน่ใจว่าพวกเขาทำสิ่งเดียวกันในกรณีนั้น!


https://en.cppreference.com/w/cpp/language/range-for :

  • ถ้า ...
  • หากrange_expressionเป็นการแสดงออกของประเภทชั้นเรียนCที่มีทั้งชื่อสมาชิกbeginและสมาชิกชื่อend(ไม่คำนึงถึงประเภทหรือการเข้าถึงของสมาชิกดังกล่าว) แล้วก็begin_exprคือ __range.begin() และend_exprเป็น__range.end();
  • มิฉะนั้นbegin_exprคือbegin(__range)และend_exprคือend(__range)ซึ่งพบได้ผ่านการค้นหาที่ขึ้นอยู่กับอาร์กิวเมนต์ (ไม่ได้ทำการค้นหาที่ไม่ใช่ ADL)

สำหรับช่วงตามลูปฟังก์ชันสมาชิกจะถูกเลือกก่อน

แต่สำหรับ

using std::begin;
begin(instance);

ฟังก์ชั่น ADL ถูกเลือกก่อน


ตัวอย่าง:

#include <iostream>
#include <string>
using std::cout;
using std::endl;

namespace Foo{
    struct A{
        //member function version
        int* begin(){
            cout << "111";
            int* p = new int(3);  //leak I know, for simplicity
            return p;
        }
        int *end(){
            cout << "111";
            int* p = new int(4);
            return p;
        }
    };

    //ADL version

    int* begin(A a){
        cout << "222";
        int* p = new int(5);
        return p;
    }

    int* end(A a){
        cout << "222";
        int* p = new int(6);
        return p;
    }

}

int main(int argc, char *args[]){
//    Uncomment only one of two code sections below for each trial

//    Foo::A a;
//    using std::begin;
//    begin(a);  //ADL version are selected. If comment out ADL version, then member functions are called.


//      Foo::A a;
//      for(auto s: a){  //member functions are selected. If comment out member functions, then ADL are called.
//      }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.