Apa alasan di balik cbegin / cend?

189

Saya bertanya-tanya mengapa cbegindan cenddiperkenalkan di C ++ 11?

Apa kasus ketika memanggil metode-metode ini membuat perbedaan dari overloads konstanta begindan end?

Andrey
sumber

Jawaban:

228

Sederhana saja. Katakanlah saya punya vektor:

std::vector<int> vec;

Saya mengisinya dengan beberapa data. Lalu saya ingin mendapatkan beberapa iterator untuk itu. Mungkin melewati mereka. Mungkin untuk std::for_each:

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

Di C ++ 03, SomeFunctorbebas untuk dapat memodifikasi parameter yang didapatnya. Tentu, SomeFunctorbisa mengambil parameternya berdasarkan nilai atau const&, tetapi tidak ada cara untuk memastikannya . Bukan tanpa melakukan sesuatu yang konyol seperti ini:

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

Sekarang, kami memperkenalkan cbegin/cend:

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

Sekarang, kami memiliki jaminan sintaksis yang SomeFunctortidak dapat memodifikasi elemen-elemen vektor (tentu saja, tanpa menggunakan konstanta). Kami secara eksplisit mendapatkan const_iterators, dan karenanya SomeFunctor::operator()akan dipanggil dengan const int &. Jika parameter itu diambil sebagai int &, C ++ akan mengeluarkan kesalahan kompiler.


C ++ 17 memiliki solusi yang lebih elegan untuk masalah ini: std::as_const. Yah, setidaknya elegan saat menggunakan berbasis rentang for:

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

Ini hanya mengembalikan a const&ke objek yang disediakan.

Nicol Bolas
sumber
1
Saya pikir protokol baru adalah cbegin (vec) daripada vec.cbegin ().
Kaz Dragon
20
@ Ka: Tidak ada std::cbegin/cendfungsi gratis seperti yang std::begin/std::endada. Itu adalah pengawasan oleh komite. Jika fungsi-fungsi itu memang ada, itu biasanya akan menjadi cara untuk menggunakannya.
Nicol Bolas
20
Rupanya, std::cbegin/cendakan ditambahkan dalam C ++ 14. Lihat en.cppreference.com/w/cpp/iterator/begin
Adi Shavit
9
@NicolBolas for(auto &item : std::as_const(vec))setara dengan for(const auto &item : vec)?
luizfls
9
@luizfls Ya. Kode Anda mengatakan bahwa item tersebut tidak akan dimodifikasi dengan meletakkan constreferensi. Nicol memandang wadah sebagai const, jadi automenyimpulkan constreferensi. IMO auto const& itemlebih mudah dan lebih jelas. Tidak jelas mengapa std::as_const()bagus di sini; Saya bisa melihatnya akan berguna ketika melewati sesuatu yang bukan constuntuk kode generik di mana kita tidak bisa mengontrol tipe yang digunakan, tetapi dengan range- for, kita bisa, jadi sepertinya ada noise tambahan di sana.
underscore_d
66

Di luar apa yang dikatakan Nicol Bolas dalam jawabannya , pertimbangkan autokata kunci baru :

auto iterator = container.begin();

Dengan auto, tidak ada cara untuk memastikan bahwa begin()mengembalikan operator konstan untuk referensi kontainer tidak konstan. Jadi sekarang Anda lakukan:

auto const_iterator = container.cbegin();
Stefan Majewsky
sumber
2
@allyourcode: Tidak membantu. Untuk kompiler, const_iteratorhanyalah pengidentifikasi lain. Versi tidak menggunakan pencarian dari typedefs anggota biasa decltype(container)::iteratoratau decltype(container)::const_iterator.
aschepler
2
@aschepler Saya tidak mengerti kalimat kedua Anda, tetapi saya pikir Anda melewatkan "const" di depan "auto" di pertanyaan saya. Apapun auto datang ke, tampaknya const_iterator haruslah const.
allyourcode
26
@allyourcode: Itu akan memberi Anda iterator yang konstan, tapi itu sangat berbeda dari iterator ke data konstan.
aschepler
2
Ada cara sederhana untuk memastikan bahwa Anda mendapatkan const_iteratordengan auto: Menulis fungsi template tambahan yang disebut make_constlolos argumen objek.
Columbo
17
Mungkin saya tidak lagi berada dalam pola pikir C ++, tetapi saya tidak dapat melihat hubungan antara konsep "cara sederhana" dan "menulis templat fungsi bantu". ;)
Stefan Majewsky
15

Anggap ini sebagai usecase praktis

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

Penugasan gagal karena itmerupakan iterator nonconst. Jika Anda menggunakan cbegin pada awalnya, iterator akan memiliki tipe yang tepat.

Johannes Schaub - litb
sumber
8

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

sehingga seorang programmer dapat langsung mendapatkan const_iterator bahkan dari wadah non-const

Mereka memberi contoh ini

vector<MyType> v;

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

Namun, ketika sebuah traversal kontainer hanya dimaksudkan untuk inspeksi, itu adalah praktik yang umumnya disukai untuk menggunakan const_iterator untuk mengizinkan kompiler untuk mendiagnosis pelanggaran konstitusionalitas.

Perhatikan bahwa kertas kerja juga menyebutkan template adaptor, yang sekarang telah selesai std::begin()dan std::end()dan juga bekerja dengan array asli. Yang sesuai std::cbegin()dan std::cend()anehnya hilang pada saat ini, tetapi mereka mungkin juga ditambahkan.

TemplateRex
sumber
5

Baru saja menemukan pertanyaan ini ... Saya tahu itu sudah menjawab dan itu hanya simpul samping ...

auto const it = container.begin() adalah tipe yang berbeda auto it = container.cbegin()

perbedaan untuk int[5](menggunakan pointer, yang saya tahu tidak memiliki metode mulai tetapi menunjukkan dengan baik perbedaan ... tetapi akan bekerja di c ++ 14 untuk std::cbegin()dan std::cend(), yang pada dasarnya apa yang harus digunakan ketika ada di sini) ...

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
chris g.
sumber
2

iteratordan const_iteratormemiliki hubungan pewarisan dan konversi implisit terjadi jika dibandingkan dengan atau ditugaskan ke jenis lainnya.

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)
{
    // ...
}

Menggunakan cbegin()dan cend()akan meningkatkan kinerja dalam hal ini.

for (std::vector<T>::const_iterator it=MyVector.cbegin(); it!=MyVector.cend(); ++it)
{
    // ...
}
hkBattousai
sumber
Butuh beberapa saat untuk menyadari maksud Anda kinerja disimpan dengan menghindari konversi ketika menginisialisasi & membandingkan iterator, bukan mitos populer bahwa constmanfaat utama adalah kinerja (yang bukan: itu kode yang secara semantik benar dan aman). Tapi, sementara Anda ada benarnya, (A) automembuat itu bukan masalah; (B) dalam berbicara tentang kinerja, Anda melewatkan hal utama yang harus Anda lakukan di sini: cache enditerator dengan mendeklarasikan salinannya dalam kondisi awal forloop, & bandingkan dengan itu, alih-alih mendapatkan salinan baru dengan nilai untuk setiap iterasi. Itu akan membuat poin Anda lebih baik. : P
underscore_d
@underscore_d constpasti dapat membantu mencapai kinerja yang lebih baik, bukan karena beberapa keajaiban dalam constkata kunci itu sendiri tetapi karena kompiler dapat mengaktifkan beberapa optimasi jika tahu bahwa data tidak akan dimodifikasi, yang tidak akan mungkin dilakukan sebaliknya. Periksa bit ini dari ceramah Jason Turner untuk contoh langsung tentang ini.
brainplot
@plain otak saya tidak mengatakan itu tidak bisa. Saya mengatakan itu bukan manfaat utama dan saya pikir itu terlalu berlebihan, ketika manfaat sebenarnya adalah kode yang secara semantik benar dan aman.
underscore_d
@underscore_d Ya, saya setuju akan hal itu. Saya hanya membuatnya secara eksplisit yang constdapat (hampir secara tidak langsung) mengarah pada manfaat kinerja; kalau-kalau seseorang membaca ini mungkin berpikir "Saya tidak akan repot menambahkan constjika kode yang dihasilkan tidak terpengaruh dengan cara apa pun", yang tidak benar.
brainplot
0

sederhana, cbegin mengembalikan iterator konstan di mana mulai mengembalikan iterator

untuk pemahaman yang lebih baik mari kita ambil dua skenario di sini

skenario 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;
}

ini akan berjalan karena di sini iterator saya tidak konstan dan dapat bertambah 5

sekarang mari kita gunakan cbegin dan cend menunjukkan mereka sebagai skenario iterators konstan - 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;
}

ini tidak akan berhasil, karena Anda tidak dapat memperbarui nilai menggunakan cbegin dan cend yang mengembalikan iterator konstan

PAVAN KUMAR
sumber