Mari kita mulai membedakan antara mengamati elemen-elemen dalam wadah vs memodifikasi mereka di tempat.
Mengamati elemen-elemennya
Mari kita perhatikan contoh sederhana:
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)
cout << x << ' ';
Kode di atas mencetak elemen intdi vector:
1 3 5 7 9
Sekarang pertimbangkan kasus lain, di mana elemen vektor bukan hanya bilangan bulat sederhana, tetapi contoh kelas yang lebih kompleks, dengan konstruktor salin ubahsuaian, dll.
// 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;
}
Jika kita menggunakan for (auto x : v) {...}sintaks di atas dengan kelas baru ini:
vector<X> v = {1, 3, 5, 7, 9};
cout << "\nElements:\n";
for (auto x : v)
{
cout << x << ' ';
}
outputnya seperti:
[... 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
Karena dapat dibaca dari output, salin panggilan konstruktor dibuat selama range-based untuk iterasi loop.
Ini karena kita menangkap elemen dari wadah berdasarkan nilai
( auto xbagian dalam for (auto x : v)).
Ini adalah kode yang tidak efisien , misalnya, jika elemen-elemen ini adalah contoh std::string, alokasi memori tumpukan dapat dilakukan, dengan perjalanan mahal ke manajer memori, dll. Ini tidak berguna jika kita hanya ingin mengamati elemen-elemen dalam sebuah wadah.
Jadi, sintaks yang lebih baik tersedia: capture by constreference , yaitu const auto&:
vector<X> v = {1, 3, 5, 7, 9};
cout << "\nElements:\n";
for (const auto& x : v)
{
cout << x << ' ';
}
Sekarang hasilnya adalah:
[... copy constructor calls for vector<X> initialization ...]
Elements:
1 3 5 7 9
Tanpa panggilan konstruktor salin palsu (dan berpotensi mahal).
Jadi, ketika mengamati unsur-unsur dalam wadah (yaitu, untuk akses read-only), sintaks berikut baik-baik saja untuk sederhana murah-ke-copy jenis, seperti int, double, dll .:
for (auto elem : container)
Lain, menangkap dengan constreferensi lebih baik dalam kasus umum , untuk menghindari panggilan copy constructor yang tidak berguna (dan berpotensi mahal):
for (const auto& elem : container)
Memodifikasi elemen dalam wadah
Jika kita ingin memodifikasi elemen dalam wadah menggunakan rentang berbasis for,
sintaks di atas for (auto elem : container)dan for (const auto& elem : container)salah.
Bahkan, dalam kasus sebelumnya, elemmenyimpan salinan elemen asli, sehingga modifikasi yang dilakukan hanya hilang dan tidak disimpan secara terus-menerus dalam wadah, misalnya:
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 << ' ';
Output hanyalah urutan awal:
1 3 5 7 9
Sebaliknya, upaya menggunakan for (const auto& x : v)gagal untuk dikompilasi.
g ++ menampilkan pesan kesalahan seperti ini:
TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x'
x *= 10;
^
Pendekatan yang benar dalam hal ini diambil dengan non- constreferensi:
vector<int> v = {1, 3, 5, 7, 9};
for (auto& x : v)
x *= 10;
for (auto x : v)
cout << x << ' ';
Outputnya (seperti yang diharapkan):
10 30 50 70 90
for (auto& elem : container)Sintaks ini juga berfungsi untuk tipe yang lebih kompleks, misalnya mempertimbangkan 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 << ' ';
outputnya adalah:
Hi Bob! Hi Jeff! Hi Connie!
Kasus khusus dari iterator proxy
Misalkan kita punya vector<bool>, dan kita ingin membalikkan keadaan logis boolean dari elemen-elemennya, menggunakan sintaks di atas:
vector<bool> v = {true, false, false, true};
for (auto& x : v)
x = !x;
Kode di atas gagal dikompilasi.
g ++ menampilkan pesan kesalahan yang mirip dengan ini:
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)
^
Masalahnya adalah bahwa std::vectortemplate khusus untuk bool, dengan implementasi yang paket yang bools ruang mengoptimalkan (setiap nilai boolean disimpan dalam satu bit, delapan "boolean" bit dalam byte).
Karena itu (karena tidak mungkin untuk mengembalikan referensi ke satu bit),
vector<bool>menggunakan pola yang disebut "proxy iterator" . "Proxy iterator" adalah iterator yang, ketika didereferensi, tidak menghasilkan objek biasa bool &, melainkan mengembalikan (berdasarkan nilai) objek sementara , yang merupakan kelas proxy yang dapat dikonversibool . (Lihat juga pertanyaan ini dan jawaban terkait di sini di StackOverflow.)
Untuk memodifikasi elemen tempat vector<bool>, jenis sintaks baru (menggunakan auto&&) harus digunakan:
for (auto&& x : v)
x = !x;
Kode berikut berfungsi dengan baik:
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 << ' ';
dan output:
false true true false
Perhatikan bahwa for (auto&& elem : container)sintaks juga berfungsi dalam kasus lain iterator biasa (non-proxy) (misalnya untuk a vector<int>atau a vector<string>).
(Sebagai catatan, sintaks "mengamati" yang disebutkan di atas for (const auto& elem : container)berfungsi dengan baik juga untuk kasus proxy iterator.)
Ringkasan
Diskusi di atas dapat diringkas dalam pedoman berikut:
Untuk mengamati elemen, gunakan sintaks berikut:
for (const auto& elem : container) // capture by const reference
Jika objek murah untuk disalin (seperti ints, doubles, dll.), Dimungkinkan untuk menggunakan formulir yang sedikit disederhanakan:
for (auto elem : container) // capture by value
Untuk memodifikasi elemen di tempat, gunakan:
for (auto& elem : container) // capture by (non-const) reference
Jika wadah menggunakan "iterators proxy" (seperti std::vector<bool>), gunakan:
for (auto&& elem : container) // capture by &&
Tentu saja, jika ada kebutuhan untuk membuat salinan lokal dari elemen di dalam tubuh loop, menangkap dengan nilai ( for (auto elem : container)) adalah pilihan yang baik.
Catatan tambahan tentang kode generik
Dalam kode generik , karena kita tidak dapat membuat asumsi tentang tipe generik Tyang murah untuk disalin, dalam mode pengamatan aman untuk selalu digunakan for (const auto& elem : container).
(Ini tidak akan memicu salinan berpotensi tidak berguna yang mahal, akan berfungsi dengan baik juga untuk jenis yang murah untuk menyalin seperti int, dan juga untuk wadah yang menggunakan proxy-iterators, seperti std::vector<bool>.)
Selain itu, dalam mode modifikasi , jika kita ingin kode generik berfungsi juga dalam kasus proxy-iterator, opsi terbaik adalah for (auto&& elem : container).
(Ini akan berfungsi dengan baik juga untuk kontainer menggunakan non-proxy-iterators biasa, seperti std::vector<int>atau std::vector<string>.)
Jadi, dalam kode generik , pedoman berikut dapat diberikan:
Untuk mengamati elemen, gunakan:
for (const auto& elem : container)
Untuk memodifikasi elemen di tempat, gunakan:
for (auto&& elem : container)