Was ist der Grund für cbegin / cend?


187

Ich frage mich warum cbeginund cendwurden in C ++ 11 eingeführt?

Was sind Fälle, in denen der Aufruf dieser Methoden einen Unterschied zu konstanten Überladungen von beginund macht end?

Antworten:


226

Es ist ganz einfach. Angenommen, ich habe einen Vektor:

std::vector<int> vec;

Ich fülle es mit einigen Daten. Dann möchte ich ein paar Iteratoren dazu bringen. Vielleicht geben Sie sie weiter. Vielleicht zu std::for_each:

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

In C ++ 03 SomeFunctorwar es frei, den erhaltenen Parameter ändern zu können . Sicher, SomeFunctorkönnte seinen Parameter nach Wert oder nach nehmen const&, aber es gibt keine Möglichkeit, dies sicherzustellen . Nicht ohne so etwas Dummes zu tun:

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

Nun stellen wir vor cbegin/cend:

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

Jetzt haben wir syntaktische Zusicherungen, SomeFunctordie die Elemente des Vektors nicht ändern können (natürlich ohne const-cast). Wir bekommen explizit const_iterators und werden daher SomeFunctor::operator()mit aufgerufen const int &. Wenn die Parameter als verwendet werden int &, gibt C ++ einen Compilerfehler aus.


C ++ 17 bietet eine elegantere Lösung für dieses Problem : std::as_const. Nun, zumindest ist es elegant, wenn man Range-basiert verwendet for:

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

Dies gibt einfach ein const&an das bereitgestellte Objekt zurück.


1
Ich dachte, das neue Protokoll sei eher cbegin (vec) als vec.cbegin ().
Kaz Dragon

20
@Kaz: Es gibt keine std::cbegin/cendfreien Funktionen, wie sie std::begin/std::endexistieren. Es war ein Versehen des Komitees. Wenn diese Funktionen vorhanden wären, würden sie im Allgemeinen so verwendet.
Nicol Bolas

20
Anscheinend std::cbegin/cendwird in C ++ 14 hinzugefügt. Siehe en.cppreference.com/w/cpp/iterator/begin
Adi Shavit

8
@NicolBolas for(auto &item : std::as_const(vec))entspricht for(const auto &item : vec)?
Luizfls

8
@luizfls Ja. Ihr Code besagt, dass der Artikel nicht geändert wird, indem Sie ihn constauf die Referenz setzen. Nicol betrachtet den Container als const und autoleitet daher eine constReferenz ab. IMO auto const& itemist einfacher und klarer. Es ist unklar, warum std::as_const()hier gut ist; Ich kann sehen, dass es nützlich wäre, wenn etwas nicht constan generischen Code übergeben wird, bei dem wir den verwendeten Typ nicht steuern können, aber mit Range forkönnen wir das. Es scheint mir also nur ein zusätzliches Rauschen zu sein.
underscore_d

66

Betrachten Sie über das hinaus, was Nicol Bolas in seiner Antwort gesagt hat , das neue autoSchlüsselwort:

auto iterator = container.begin();

Mit autokann nicht sichergestellt werden, dass begin()ein konstanter Operator für eine nicht konstante Containerreferenz zurückgegeben wird. Jetzt machst du also:

auto const_iterator = container.cbegin();

2
@allyourcode: Hilft nicht. Für den Compiler const_iteratorist dies nur eine weitere Kennung. Keine der Versionen verwendet eine Suche nach den üblichen Mitgliedertypedefs decltype(container)::iteratoroder decltype(container)::const_iterator.
Aschepler

2
@aschepler Ich verstehe deinen zweiten Satz nicht, aber ich denke, du hast in meiner Frage die "const" vor "auto" verpasst. Was auch immer Auto kommt, scheint, dass const_iterator const sein sollte.
Allyourcode

26
@allyourcode: Das würde Ihnen einen Iterator geben, der konstant ist, aber das unterscheidet sich sehr von einem Iterator zu konstanten Daten.
Aschepler

2
Es gibt eine einfache Möglichkeit, um sicherzustellen, dass Sie eine const_iteratormit erhalten auto: Schreiben Sie eine Hilfsfunktionsvorlage, die aufgerufen wird make_const, um das Objektargument zu qualifizieren.
Columbo

17
Vielleicht bin ich einfach nicht mehr in der C ++ - Denkweise, aber ich kann keinen Zusammenhang zwischen den Konzepten "auf einfache Weise" und "Schreiben einer Hilfsfunktionsvorlage" erkennen. ;)
Stefan Majewsky

15

Nehmen Sie dies als praktischen Anwendungsfall

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

Die Zuweisung schlägt fehl, da ites sich um einen nicht konstanten Iterator handelt. Wenn Sie anfänglich cbegin verwendet hätten, hätte der Iterator den richtigen Typ gehabt.


8

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

Damit kann ein Programmierer einen const_iterator auch aus einem Nicht-const-Container direkt abrufen

Sie gaben dieses Beispiel

vector<MyType> v;

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

Wenn eine Containerüberquerung jedoch nur zur Überprüfung vorgesehen ist, ist es allgemein bevorzugt, einen const_iterator zu verwenden, damit der Compiler Verstöße gegen die Konstanzkorrektheit diagnostizieren kann

Beachten Sie, dass im Arbeitspapier auch Adaptervorlagen erwähnt werden, die jetzt als std::begin()und finalisiert wurden std::end()und auch mit nativen Arrays funktionieren. Die entsprechenden std::cbegin()und std::cend()fehlen ab diesem Zeitpunkt merkwürdigerweise, können aber auch hinzugefügt werden.


5

Ich bin gerade auf diese Frage gestoßen ... Ich weiß, dass sie bereits beantwortet wurde und nur ein Seitenknoten ist ...

auto const it = container.begin() ist dann ein anderer Typ auto it = container.cbegin()

der Unterschied für int[5](mit Zeiger, von dem ich weiß, dass er nicht die Methode begin hat, aber den Unterschied gut zeigt ... aber in c ++ 14 für std::cbegin()und funktionieren würde std::cend(), was man im Wesentlichen verwenden sollte, wenn er hier ist) ...

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

iteratorund const_iteratorVererbungsbeziehung haben und eine implizite Konvertierung erfolgt, wenn sie mit dem anderen Typ verglichen oder diesem zugewiesen wird.

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

Die Verwendung von cbegin()und cend()erhöht in diesem Fall die Leistung.

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

Es hat eine Weile gedauert, bis mir klar wurde, dass Sie die Leistung sparen, indem Sie beim Initialisieren und Vergleichen von Iteratoren keine Konvertierung durchführen. Dies ist nicht der beliebte Mythos, dessen constHauptvorteil die Leistung ist (was nicht der Fall ist : semantisch korrekter und sicherer Code). Aber während Sie einen Punkt haben, macht (A) autodas zu einem Nicht-Problem; (B) Wenn Sie über die Leistung sprechen, haben Sie eine wichtige Sache übersehen, die Sie hier hätten tun sollen: Zwischenspeichern Sie den endIterator, indem Sie eine Kopie davon im Init-Zustand der forSchleife deklarieren , und vergleichen Sie diese, anstatt eine neue Kopie von zu erhalten Wert für jede Iteration. Das wird Ihren Standpunkt verbessern. : P
underscore_d

@underscore_d constkann definitiv dazu beitragen, eine bessere Leistung zu erzielen, nicht aufgrund von Magie im constSchlüsselwort selbst, sondern weil der Compiler einige Optimierungen aktivieren kann, wenn er weiß, dass die Daten nicht geändert werden, was sonst nicht möglich wäre. Schauen Sie sich dieses Stück aus einem Vortrag von Jason Turner an, um ein Live-Beispiel dafür zu erhalten.
Brainplot

@ Brainplot Ich habe nicht gesagt, dass es nicht kann. Ich sagte, dass dies nicht der Hauptvorteil ist und dass es meiner Meinung nach überbewertet wird, wenn der eigentliche Vorteil davon semantisch korrekter und sicherer Code ist.
underscore_d

@underscore_d Ja, da stimme ich zu. Ich habe nur deutlich gemacht, dass constdies (fast indirekt) zu Leistungsvorteilen führen kann. Nur für den Fall, dass jemand, der dies liest, denkt: "Ich werde mich nicht darum kümmern, etwas hinzuzufügen, constwenn der generierte Code in keiner Weise beeinflusst wird", was nicht stimmt.
Brainplot

0

Sein einfacher cbegin gibt einen konstanten Iterator zurück, wobei begin nur einen Iterator zurückgibt

Zum besseren Verständnis nehmen wir hier zwei Szenarien

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

Dies wird ausgeführt, da hier der Iterator i nicht konstant ist und um 5 erhöht werden kann

Verwenden wir nun cbegin und cend, um sie als konstantes Iteratorszenario zu bezeichnen - 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;
}

Dies wird nicht funktionieren, da Sie den Wert nicht mit cbegin und cend aktualisieren können, wodurch der konstante Iterator zurückgegeben wird

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.