Kaptaki öğeleri gözlemlemek ve bunları yerinde değiştirmek arasında ayrım yapmaya başlayalım .
Elemanları gözlemleme
Basit bir örneği ele alalım:
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)
cout << x << ' ';
Yukarıdaki kod elemanlarını (basar int
s) vector
:
1 3 5 7 9
Şimdi, vektör öğelerinin sadece basit tamsayılar değil, özel kopya oluşturucu vb. İle daha karmaşık bir sınıfın örnekleri olduğu başka bir durumu düşünün.
// A sample test class, with custom copy semantics.
class X
{
public:
X()
: m_data(0)
{}
X(int data)
: m_data(data)
{}
~X()
{}
X(const X& other)
: m_data(other.m_data)
{ cout << "X copy ctor.\n"; }
X& operator=(const X& other)
{
m_data = other.m_data;
cout << "X copy assign.\n";
return *this;
}
int Get() const
{
return m_data;
}
private:
int m_data;
};
ostream& operator<<(ostream& os, const X& x)
{
os << x.Get();
return os;
}
Yukarıdaki for (auto x : v) {...}
sözdizimini bu yeni sınıfla kullanırsak:
vector<X> v = {1, 3, 5, 7, 9};
cout << "\nElements:\n";
for (auto x : v)
{
cout << x << ' ';
}
çıktı şöyle bir şeydir:
[... copy constructor calls for vector<X> initialization ...]
Elements:
X copy ctor.
1 X copy ctor.
3 X copy ctor.
5 X copy ctor.
7 X copy ctor.
9
Çıktıdan okunabildiğinden, döngü yinelemeleri için aralık tabanlı sırasında kopya yapıcı çağrıları yapılır.
Biz Bunun nedeni, yakalama kaptan elemanları değeri
( auto x
kısmı içindefor (auto x : v)
).
Bu, verimsiz bir koddur, örneğin, bu elemanların örnekleri ise std::string
, yığın bellek tahsisi yapılabilir, bellek yöneticisine pahalı geziler vb. Yapılabilir. Bu sadece bir kaptaki elemanları gözlemlemek istiyorsak işe yaramaz .
Yani, daha iyi bir sözdizimi mevcuttur: Yakalama tarafından const
referans , yani const auto&
:
vector<X> v = {1, 3, 5, 7, 9};
cout << "\nElements:\n";
for (const auto& x : v)
{
cout << x << ' ';
}
Şimdi çıktı:
[... copy constructor calls for vector<X> initialization ...]
Elements:
1 3 5 7 9
Sahte (ve potansiyel olarak pahalı) bir kopya oluşturucu çağrısı olmadan.
Yani, gözlemleyerek bir kap (yani salt okunur erişim için) elementler, aşağıdaki sözdizimi basit için gayet ucuz-to-kopya gibi türleri int
, double
vb .:
for (auto elem : container)
Aksi takdirde , işe yaramaz (ve potansiyel olarak pahalı) kopya yapıcı çağrılarını önlemek const
için referans olarak yakalama genel durumda daha iyidir :
for (const auto& elem : container)
Kaptaki öğeleri değiştirme
Kapsayıcıdaki öğeleri aralık tabanlı kullanarak değiştirmek istiyorsak for
, yukarıdaki for (auto elem : container)
ve for (const auto& elem : container)
sözdizimleri yanlıştır.
Aslında, önceki durumda, orijinal öğenin elem
bir kopyasını saklar , böylece üzerinde yapılan değişiklikler kaybolur ve kapta kalıcı olarak saklanmaz, örneğin:
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v) // <-- capture by value (copy)
x *= 10; // <-- a local temporary copy ("x") is modified,
// *not* the original vector element.
for (auto x : v)
cout << x << ' ';
Çıktı sadece başlangıç dizisidir:
1 3 5 7 9
Bunun yerine, kullanma girişimi for (const auto& x : v)
derlenemez.
g ++ şöyle bir hata mesajı verir:
TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x'
x *= 10;
^
Bu durumda doğru yaklaşım const
referanssız olarak yakalanmaktadır :
vector<int> v = {1, 3, 5, 7, 9};
for (auto& x : v)
x *= 10;
for (auto x : v)
cout << x << ' ';
Çıktı (beklendiği gibi):
10 30 50 70 90
Bu for (auto& elem : container)
sözdizimi daha karmaşık türler için de kullanılabilir, örneğin vector<string>
:
vector<string> v = {"Bob", "Jeff", "Connie"};
// Modify elements in place: use "auto &"
for (auto& x : v)
x = "Hi " + x + "!";
// Output elements (*observing* --> use "const auto&")
for (const auto& x : v)
cout << x << ' ';
çıktı:
Hi Bob! Hi Jeff! Hi Connie!
Proxy yineleyicilerinin özel durumu
Varsayalım vector<bool>
ve yukarıdaki sözdizimini kullanarak öğelerinin mantıksal boole durumunu ters çevirmek istiyoruz:
vector<bool> v = {true, false, false, true};
for (auto& x : v)
x = !x;
Yukarıdaki kod derlenemedi.
g ++ aşağıdakine benzer bir hata iletisi verir:
TestRangeFor.cpp:168:20: error: invalid initialization of non-const reference of
type 'std::_Bit_reference&' from an rvalue of type 'std::_Bit_iterator::referen
ce {aka std::_Bit_reference}'
for (auto& x : v)
^
Sorun, std::vector
şablonun alanı optimize etmek için s paketleyen bir uygulamayla (her boolean değeri bir bitte, sekiz "boolean" biti bir baytta saklanması) için uzman olmasıdır .bool
bool
(Tek bit başvuru döndürmek mümkün değildir beri) Bu nedenle,
vector<bool>
sözde kullanır "vekil yineleyici" desen. "Proxy yineleyici", kayıttan çıkarıldığında sıradan bir sonuç vermeyenbool &
, bunun yerine dönüştürülebilir bir proxy sınıfı olan geçici bir nesneyi (değere göre) döndüren bir yineleyicidir . (Ayrıca bu soruya ve ilgili cevaplara bakınız)bool
StackOverflow'daki .)
Öğelerini yerinde değiştirmek vector<bool>
için yeni bir tür sözdizimi (kullanma auto&&
) kullanılmalıdır:
for (auto&& x : v)
x = !x;
Aşağıdaki kod iyi çalışıyor:
vector<bool> v = {true, false, false, true};
// Invert boolean status
for (auto&& x : v) // <-- note use of "auto&&" for proxy iterators
x = !x;
// Print new element values
cout << boolalpha;
for (const auto& x : v)
cout << x << ' ';
ve çıktılar:
false true true false
for (auto&& elem : container)
Sözdiziminin diğer sıradan (proxy olmayan) yineleyicilerde de işe yaradığını unutmayın (örneğin a vector<int>
veya avector<string>
).
(Bir yan not olarak, yukarıda belirtilen "gözlemleme" sözdizimi for (const auto& elem : container)
, proxy yineleyici durumu için de işe yarar.)
özet
Yukarıdaki tartışma aşağıdaki kılavuzlarda özetlenebilir:
İçin gözlem elemanları aşağıdaki sözdizimini kullanın:
for (const auto& elem : container) // capture by const reference
Nesnelerin kopyalanması ucuzsa ( int
s, double
s vb.), Biraz basitleştirilmiş bir form kullanmak mümkündür:
for (auto elem : container) // capture by value
İçin modifiye yer, kullanımda unsurları:
for (auto& elem : container) // capture by (non-const) reference
Kapsayıcı "proxy yineleyicileri" kullanıyorsa (gibi std::vector<bool>
), şunu kullanın:
for (auto&& elem : container) // capture by &&
Elbette , öğenin döngü gövdesi içinde yerel bir kopyasını yapmaya ihtiyaç varsa , değer ( for (auto elem : container)
) ile yakalamak iyi bir seçimdir.
Genel kod hakkında ek notlar
Gelen jenerik kod biz genel tür hakkında varsayımlar yapamazsınız çünkü T
kopyasına ucuz olma, içinde gözlemleyerek modunu her zaman kullanmak güvenlidir for (const auto& elem : container)
.
(Bu, potansiyel olarak pahalı yararsız kopyaları tetiklemez, aynı zamanda kopyalanması ucuz türler int
için ve ayrıca proxy yineleyicileri kullanan kaplar için de iyi çalışır std::vector<bool>
.)
Ayrıca, mod değiştirme modunda, genel kodun proxy yineleyiciler durumunda da çalışmasını istiyorsak , en iyi seçenek for (auto&& elem : container)
.
(Bu, std::vector<int>
veya gibi sıradan proxy olmayan yineleyiciler kullanan kaplar için de iyi çalışır std::vector<string>
.)
Dolayısıyla, genel kodda aşağıdaki yönergeler sağlanabilir:
İçin gözlem elemanları, kullanın:
for (const auto& elem : container)
İçin modifiye yer, kullanımda unsurları:
for (auto&& elem : container)