เหตุผลที่อยู่เบื้องหลัง cbegin / cend คืออะไร?


คำตอบ:


227

มันค่อนข้างง่าย พูดว่าฉันมีเวกเตอร์:

std::vector<int> vec;

ฉันเติมด้วยข้อมูลบางส่วน จากนั้นฉันอยากได้ตัววนซ้ำไป อาจผ่านพวกเขาไปรอบ ๆ อาจจะstd::for_each:

std::for_each(vec.begin(), vec.end(), SomeFunctor());

ใน C ++ 03 SomeFunctorมีอิสระที่จะสามารถแก้ไขพารามิเตอร์ที่ได้รับ แน่นอนว่าSomeFunctorสามารถใช้พารามิเตอร์ตามค่าหรือตามconst&แต่ไม่มีวิธีที่จะทำให้แน่ใจได้ ไม่ได้ทำอะไรโง่ ๆ แบบนี้:

const std::vector<int> &vec_ref = vec;
std::for_each(vec_ref.begin(), vec_ref.end(), SomeFunctor());

ตอนนี้เราแนะนำcbegin/cend:

std::for_each(vec.cbegin(), vec.cend(), SomeFunctor());

ตอนนี้เรามีการรับรองทางวากยสัมพันธ์ที่SomeFunctorไม่สามารถแก้ไของค์ประกอบของเวกเตอร์ (แน่นอนว่าไม่มีนักแสดง) เราได้รับอย่างชัดเจนconst_iterators และดังนั้นจึงจะเรียกว่ามีSomeFunctor::operator() const int &หากใช้เป็นพารามิเตอร์int &C ++ จะออกข้อผิดพลาดของคอมไพเลอร์


C ++ 17 มีวิธีแก้ไขปัญหาที่หรูหรากว่านี้: std::as_const. อย่างน้อยก็สง่างามเมื่อใช้ช่วงแบบfor:

for(auto &item : std::as_const(vec))

สิ่งนี้จะคืนค่า a const&ไปยังวัตถุที่ถูกจัดเตรียมไว้


1
ฉันคิดว่าโปรโตคอลใหม่คือ cbegin (vec) มากกว่า vec.cbegin ()
Kaz Dragon

20
@Kaz: ไม่มีstd::cbegin/cendฟังก์ชั่นฟรีในแบบที่std::begin/std::endมีอยู่ มันเป็นการกำกับดูแลของคณะกรรมการ หากมีฟังก์ชั่นเหล่านั้นอยู่นั่นก็เป็นวิธีการใช้งาน
Nicol Bolas

20
เห็นได้ชัดว่าstd::cbegin/cendจะถูกเพิ่มใน C ++ 14 ดูen.cppreference.com/w/cpp/iterator/begin
Adi Shavit

8
@ NicolBolas for(auto &item : std::as_const(vec))เทียบเท่ากับfor(const auto &item : vec)?
luizfls

8
@luizfls ใช่ รหัสของคุณระบุว่ารายการจะไม่ถูกแก้ไขโดยการใส่constข้อมูลอ้างอิง Nicol มองว่าคอนเทนเนอร์เป็น const ดังนั้นautoอนุมานconstข้อมูลอ้างอิง IMO auto const& itemนั้นง่ายและชัดเจนยิ่งขึ้น มันไม่มีความชัดเจนว่าทำไมถึงstd::as_const()ดีที่นี่; ฉันเห็นว่ามันจะมีประโยชน์เมื่อส่งบางสิ่งที่ไม่ใช่constรหัสทั่วไปซึ่งเราไม่สามารถควบคุมประเภทที่ใช้งานได้ แต่ด้วยช่วงfor- เราทำได้ดังนั้นดูเหมือนว่าจะเพิ่มเสียงรบกวนให้ฉันที่นั่น
underscore_d

66

นอกเหนือจากที่ Nicol Bolas พูดไว้ในคำตอบของเขาแล้วลองพิจารณาautoคำค้นหาใหม่:

auto iterator = container.begin();

ด้วยautoไม่มีวิธีที่จะทำให้แน่ใจว่าbegin()จะส่งกลับผู้ประกอบการคงที่สำหรับการอ้างอิงภาชนะไม่คงที่ ดังนั้นตอนนี้คุณทำ:

auto const_iterator = container.cbegin();

2
@allyourcode: ไม่ช่วย สำหรับคอมไพเลอร์const_iteratorเป็นเพียงตัวระบุอื่น รุ่นทั้งใช้การค้นหาของ typedefs สมาชิกปกติหรือdecltype(container)::iterator decltype(container)::const_iterator
aschepler

2
@aschepler ฉันไม่เข้าใจประโยคที่สองของคุณ แต่ฉันคิดว่าคุณพลาด "const" ต่อหน้า "auto" ในคำถามของฉัน ดูเหมือนว่า const_iterator ควรเป็น const
allyourcode

26
@allyourcode: นั่นจะให้ตัววนซ้ำซึ่งเป็นค่าคงที่ แต่นั่นแตกต่างจากตัววนซ้ำไปเป็นข้อมูลคงที่
aschepler

2
มีวิธีง่ายๆในการตรวจสอบให้แน่ใจว่าคุณได้รับconst_iteratorกับauto: เขียนเทมเพลตฟังก์ชั่นเสริมที่เรียกว่าmake_constมีคุณสมบัติอาร์กิวเมนต์วัตถุ
Columbo

17
บางทีฉันอาจไม่ได้อยู่ใน C ++ mindset อีกต่อไป แต่ฉันไม่เห็นการเชื่อมต่อระหว่างแนวคิดของ "วิธีง่ายๆ" และ "เขียนเทมเพลตฟังก์ชั่นเสริม" ;)
Stefan Majewsky

15

ใช้สิ่งนี้เป็น usecase ในทางปฏิบัติ

void SomeClass::f(const vector<int>& a) {
  auto it = someNonConstMemberVector.begin();
  ...
  it = a.begin();
  ...
}

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


8

จากhttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1674.pdf :

เพื่อให้โปรแกรมเมอร์สามารถรับ const_iterator ได้โดยตรงจากแม้แต่คอนเทนเนอร์ที่ไม่ใช่ const

พวกเขายกตัวอย่างนี้

vector<MyType> v;

// fill v ...
typedef vector<MyType>::iterator iter;
for( iter it = v.begin(); it != v.end(); ++it ) {
    // use *it ...
}

อย่างไรก็ตามเมื่อ traversal ของคอนเทนเนอร์มีไว้สำหรับการตรวจสอบเท่านั้นมันเป็นวิธีปฏิบัติที่นิยมใช้ในการใช้ const_iterator เพื่อให้คอมไพเลอร์วินิจฉัยการละเมิดความถูกต้องของ const

โปรดทราบว่ากระดาษทำงานยังกล่าวถึงแม่แบบของอะแดปเตอร์ซึ่งตอนนี้ได้รับการสรุปstd::begin()และstd::end()และยังทำงานกับอาร์เรย์พื้นเมือง ที่เกี่ยวข้องstd::cbegin()และstd::cend()หายไปอยากรู้อยากเห็น ณ เวลานี้ แต่พวกเขาอาจจะเพิ่ม


5

เพิ่งสะดุดกับคำถามนี้ ... ฉันรู้ว่ามันเป็นคำตอบ alredy และมันเป็นเพียงโหนดด้านข้าง ...

auto const it = container.begin() เป็นประเภทที่แตกต่างกันไปแล้ว auto it = container.cbegin()

ความแตกต่างสำหรับint[5](ใช้ตัวชี้ซึ่งฉันรู้ว่าไม่มีวิธีการเริ่มต้น แต่แสดงความแตกต่างอย่างชัดเจน ... แต่จะทำงานใน c ++ 14 สำหรับstd::cbegin()และstd::cend()ซึ่งเป็นหลักสิ่งที่ควรใช้เมื่ออยู่ที่นี่) ...

int numbers = array[7];
const auto it = begin(numbers); // type is int* const -> pointer is const
auto it = cbegin(numbers);      // type is int const* -> value is const

2

iteratorและconst_iteratorมีความสัมพันธ์ทางมรดกและการแปลงโดยนัยเกิดขึ้นเมื่อเปรียบเทียบกับหรือกำหนดให้กับประเภทอื่น

class T {} MyT1, MyT2, MyT3;
std::vector<T> MyVector = {MyT1, MyT2, MyT3};
for (std::vector<T>::const_iterator it=MyVector.begin(); it!=MyVector.end(); ++it)
{
    // ...
}

การใช้cbegin()และcend()จะเพิ่มประสิทธิภาพในกรณีนี้

for (std::vector<T>::const_iterator it=MyVector.cbegin(); it!=MyVector.cend(); ++it)
{
    // ...
}

ฉันใช้เวลาสักพักกว่าจะรู้ว่าคุณหมายถึงประสิทธิภาพถูกบันทึกไว้โดยหลีกเลี่ยงการแปลงเมื่อเริ่มต้นและเปรียบเทียบตัววนซ้ำไม่ใช่ตำนานที่เป็นที่นิยมซึ่งconstประโยชน์หลักคือประสิทธิภาพ (ซึ่งไม่ใช่: เป็นรหัสที่ถูกต้องและปลอดภัย) แต่ในขณะที่คุณมีจุด (A) autoทำให้มันไม่ใช่ประเด็น; (B) ในการพูดคุยเกี่ยวกับประสิทธิภาพคุณพลาดสิ่งสำคัญที่คุณควรทำที่นี่: แคชตัวendวนซ้ำโดยการประกาศสำเนาในสภาพ init ของforลูป & เปรียบเทียบกับมันแทนที่จะได้รับสำเนาใหม่โดย ค่าสำหรับการวนซ้ำทุกครั้ง นั่นจะทำให้จุดของคุณดีขึ้น : P
underscore_d

@underscore_d constสามารถช่วยให้ได้ประสิทธิภาพที่ดีขึ้นอย่างแน่นอนไม่ใช่เพราะเวทมนตร์บางอย่างในconstคำหลักนั้นเอง แต่เนื่องจากคอมไพเลอร์สามารถเปิดใช้งานการเพิ่มประสิทธิภาพบางอย่างหากรู้ว่าข้อมูลจะไม่ถูกแก้ไขซึ่งจะเป็นไปไม่ได้ ตรวจสอบบิตนี้จากการพูดคุยของ Jason Turner สำหรับตัวอย่างสดของสิ่งนี้
brainplot

@brainplot ฉันไม่ได้บอกว่าทำไม่ได้ ฉันบอกว่านั่นไม่ใช่ประโยชน์หลักและฉันคิดว่ามันเกินจริงเมื่อประโยชน์ที่แท้จริงของมันคือรหัสที่ถูกต้องและปลอดภัย
underscore_d

@underscore_d ใช่ฉันเห็นด้วยกับสิ่งนั้น ฉันแค่ทำให้ชัดเจนว่าconstสามารถนำไปสู่ประโยชน์ด้านประสิทธิภาพ ในกรณีที่บางคนที่อ่านข้อความนี้อาจคิดว่า "ฉันจะไม่รบกวนการเพิ่มconstหากโค้ดที่สร้างขึ้นไม่ได้รับผลกระทบ แต่อย่างใด" ซึ่งไม่เป็นความจริง
Brainplot

0

มันง่าย cbegin ส่งคืนตัววนซ้ำค่าคงที่ซึ่งเริ่มต้นส่งคืนตัววนซ้ำ

เพื่อความเข้าใจที่ดีขึ้นลองทำสองสถานการณ์ที่นี่

สถานการณ์ - 1:

#include <iostream>
using namespace std;
#include <vector>
int main(int argc, char const *argv[])
{
std::vector<int> v;

for (int i = 1; i < 6; ++i)
{
    /* code */
    v.push_back(i);
}

for(auto i = v.begin();i< v.end();i++){
    *i = *i + 5;
}

for (auto i = v.begin();i < v.end();i++){
    cout<<*i<<" ";
}

return 0;
}

สิ่งนี้จะทำงานเพราะที่นี่ตัววนซ้ำ i ไม่คงที่และสามารถเพิ่มขึ้นได้ 5

ตอนนี้เรามาใช้ cbegin และ cend แทนพวกมันว่าเป็นตัววนซ้ำค่าคงที่ - 2:

#include <iostream>
using namespace std;
#include <vector>
int main(int argc, char const *argv[])
{
std::vector<int> v;

for (int i = 1; i < 6; ++i)
{
    /* code */
    v.push_back(i);
}

for(auto i = v.cbegin();i< v.cend();i++){
    *i = *i + 5;
}

for (auto i = v.begin();i < v.end();i++){
    cout<<*i<<" ";
}

return 0;
}

สิ่งนี้จะไม่ทำงานเนื่องจากคุณไม่สามารถอัปเดตค่าโดยใช้ cbegin และ cend ซึ่งจะคืนค่าตัววนซ้ำคงที่

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