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 int
di 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 x
bagian 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 const
reference , 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 const
referensi 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, elem
menyimpan 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- const
referensi:
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::vector
template khusus untuk bool
, dengan implementasi yang paket yang bool
s 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 int
s, double
s, 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 T
yang 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)