Antworten:
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 SomeFunctor
war es frei, den erhaltenen Parameter ändern zu können . Sicher, SomeFunctor
kö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, SomeFunctor
die die Elemente des Vektors nicht ändern können (natürlich ohne const-cast). Wir bekommen explizit const_iterator
s 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.
std::cbegin/cend
freien Funktionen, wie sie std::begin/std::end
existieren. Es war ein Versehen des Komitees. Wenn diese Funktionen vorhanden wären, würden sie im Allgemeinen so verwendet.
std::cbegin/cend
wird in C ++ 14 hinzugefügt. Siehe en.cppreference.com/w/cpp/iterator/begin
for(auto &item : std::as_const(vec))
entspricht for(const auto &item : vec)
?
const
auf die Referenz setzen. Nicol betrachtet den Container als const und auto
leitet daher eine const
Referenz ab. IMO auto const& item
ist 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 const
an generischen Code übergeben wird, bei dem wir den verwendeten Typ nicht steuern können, aber mit Range for
können wir das. Es scheint mir also nur ein zusätzliches Rauschen zu sein.
Betrachten Sie über das hinaus, was Nicol Bolas in seiner Antwort gesagt hat , das neue auto
Schlüsselwort:
auto iterator = container.begin();
Mit auto
kann 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();
const_iterator
ist dies nur eine weitere Kennung. Keine der Versionen verwendet eine Suche nach den üblichen Mitgliedertypedefs decltype(container)::iterator
oder decltype(container)::const_iterator
.
const_iterator
mit erhalten auto
: Schreiben Sie eine Hilfsfunktionsvorlage, die aufgerufen wird make_const
, um das Objektargument zu qualifizieren.
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 it
es sich um einen nicht konstanten Iterator handelt. Wenn Sie anfänglich cbegin verwendet hätten, hätte der Iterator den richtigen Typ gehabt.
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.
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
iterator
und const_iterator
Vererbungsbeziehung 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)
{
// ...
}
const
Hauptvorteil die Leistung ist (was nicht der Fall ist : semantisch korrekter und sicherer Code). Aber während Sie einen Punkt haben, macht (A) auto
das 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 end
Iterator, indem Sie eine Kopie davon im Init-Zustand der for
Schleife deklarieren , und vergleichen Sie diese, anstatt eine neue Kopie von zu erhalten Wert für jede Iteration. Das wird Ihren Standpunkt verbessern. : P
const
kann definitiv dazu beitragen, eine bessere Leistung zu erzielen, nicht aufgrund von Magie im const
Schlü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.
const
dies (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, const
wenn der generierte Code in keiner Weise beeinflusst wird", was nicht stimmt.
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