Operator umum kelebihan beban
Sebagian besar pekerjaan di operator kelebihan muatan adalah kode boiler-plate. Itu tidak mengherankan, karena operator hanyalah gula sintaksis, pekerjaan mereka yang sebenarnya dapat dilakukan dengan (dan sering diteruskan ke) fungsi sederhana. Tetapi penting bahwa Anda mendapatkan kode pelat ketel ini dengan benar. Jika Anda gagal, kode operator Anda tidak dapat dikompilasi atau kode pengguna Anda tidak akan dikompilasi atau kode pengguna Anda akan berperilaku mengejutkan.
Operator Penugasan
Ada banyak yang bisa dikatakan tentang tugas. Namun, sebagian besar sudah dikatakan di FAQ Copy-and-Swap terkenal GMan , jadi saya akan melewatkan sebagian besar di sini, hanya daftar operator penugasan yang sempurna untuk referensi:
X& X::operator=(X rhs)
{
swap(rhs);
return *this;
}
Operator Bitshift (digunakan untuk Stream I / O)
Operator bitshift <<
dan >>
, meskipun masih digunakan dalam interfacing perangkat keras untuk fungsi manipulasi bit yang mereka warisi dari C, telah menjadi lebih lazim sebagai operator input dan output stream yang kelebihan beban di sebagian besar aplikasi. Untuk panduan kelebihan muatan sebagai operator manipulasi bit, lihat bagian di bawah ini tentang Operator Aritmatika Biner. Untuk menerapkan format kustom dan logika parsing saat objek Anda digunakan dengan iostreams, lanjutkan.
Operator stream, di antara operator yang paling sering kelebihan beban, adalah operator infiks biner dimana sintaksnya tidak menetapkan batasan apakah mereka harus anggota atau bukan. Karena mereka mengubah argumen kiri mereka (mereka mengubah keadaan aliran), mereka harus, menurut aturan praktis, diimplementasikan sebagai anggota tipe operan kiri mereka. Namun, operan kiri mereka adalah stream dari library standar, dan sementara sebagian besar output stream dan operator input didefinisikan oleh library standar memang didefinisikan sebagai anggota kelas stream, ketika Anda mengimplementasikan operasi output dan input untuk tipe Anda sendiri, Anda tidak dapat mengubah jenis aliran perpustakaan standar. Itu sebabnya Anda perlu mengimplementasikan operator ini untuk tipe Anda sendiri sebagai fungsi non-anggota. Bentuk kanonik keduanya adalah sebagai berikut:
std::ostream& operator<<(std::ostream& os, const T& obj)
{
// write obj to stream
return os;
}
std::istream& operator>>(std::istream& is, T& obj)
{
// read obj from stream
if( /* no valid object of T found in stream */ )
is.setstate(std::ios::failbit);
return is;
}
Saat menerapkan operator>>
, mengatur keadaan aliran secara manual hanya diperlukan ketika pembacaan itu sendiri berhasil, tetapi hasilnya tidak seperti yang diharapkan.
Operator panggilan fungsi
Operator fungsi panggilan, yang digunakan untuk membuat objek fungsi, juga dikenal sebagai functors, harus didefinisikan sebagai fungsi anggota , sehingga selalu memiliki this
argumen implisit fungsi anggota. Selain ini, dapat kelebihan beban untuk mengambil sejumlah argumen tambahan, termasuk nol.
Berikut ini contoh sintaksnya:
class foo {
public:
// Overloaded call operator
int operator()(const std::string& y) {
// ...
}
};
Pemakaian:
foo f;
int a = f("hello");
Di seluruh pustaka standar C ++, objek fungsi selalu disalin. Objek fungsi Anda sendiri karenanya harus murah untuk disalin. Jika objek fungsi benar-benar perlu menggunakan data yang mahal untuk disalin, lebih baik menyimpan data itu di tempat lain dan meminta objek fungsi merujuknya.
Operator pembanding
Operator pembanding infiks biner harus, sesuai dengan aturan praktis, diimplementasikan sebagai fungsi non-anggota 1 . Negasi awalan unary !
harus (sesuai dengan aturan yang sama) diimplementasikan sebagai fungsi anggota. (tapi biasanya itu bukan ide yang baik untuk membebani itu.)
Algoritme pustaka standar (mis. std::sort()
) Dan tipe (mis std::map
) akan selalu hanya berharap operator<
untuk hadir. Namun, pengguna tipe Anda akan mengharapkan semua operator lain juga hadir , jadi jika Anda mendefinisikan operator<
, pastikan untuk mengikuti aturan mendasar ketiga dari operator yang berlebihan dan juga mendefinisikan semua operator perbandingan boolean lainnya. Cara kanonik untuk mengimplementasikannya adalah ini:
inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}
Yang penting untuk dicatat di sini adalah bahwa hanya dua dari operator ini yang benar-benar melakukan apa saja, yang lain hanya meneruskan argumen mereka ke salah satu dari dua ini untuk melakukan pekerjaan yang sebenarnya.
Sintaks untuk overloading operator boolean biner yang tersisa ( ||
, &&
) mengikuti aturan operator perbandingan. Namun, sangat tidak mungkin bahwa Anda akan menemukan use case yang masuk akal untuk 2 ini .
1 Seperti semua aturan praktis lainnya, terkadang mungkin ada alasan untuk melanggarnya. Jika demikian, jangan lupa bahwa operan kiri dari operator perbandingan biner, yang untuk fungsi anggota akan *this
, perlu const
juga demikian. Jadi operator perbandingan yang diimplementasikan sebagai fungsi anggota harus memiliki tanda tangan ini:
bool operator<(const X& rhs) const { /* do actual comparison with *this */ }
(Catat const
di bagian akhir.)
2 Perlu dicatat bahwa versi built-in ||
dan &&
menggunakan cara pintas semantik. Sementara yang ditentukan pengguna (karena mereka adalah sintaksis gula untuk panggilan metode) tidak menggunakan semantik pintasan. Pengguna akan mengharapkan operator ini untuk memiliki semantik pintasan, dan kode mereka mungkin bergantung padanya, Oleh karena itu sangat disarankan untuk tidak mendefinisikannya.
Operator Aritmatika
Operator aritmatika unary
Operator kenaikan dan penurunan unary datang dalam rasa awalan dan postfix. Untuk membedakan satu dari yang lain, varian postfix mengambil argumen int dummy tambahan. Jika Anda menambah atau mengurangi secara berlebihan, pastikan untuk selalu menerapkan versi awalan dan postfix. Berikut ini adalah implementasi kenaikan kanonik, penurunan mengikuti aturan yang sama:
class X {
X& operator++()
{
// do actual increment
return *this;
}
X operator++(int)
{
X tmp(*this);
operator++();
return tmp;
}
};
Perhatikan bahwa varian postfix diimplementasikan dalam bentuk awalan. Perhatikan juga bahwa postfix melakukan salinan tambahan. 2
Overload minus dan plus unary tidak terlalu umum dan mungkin sebaiknya dihindari. Jika diperlukan, mereka mungkin harus kelebihan beban sebagai fungsi anggota.
2 Juga perhatikan bahwa varian postfix bekerja lebih banyak dan karenanya kurang efisien untuk digunakan daripada varian prefix. Ini adalah alasan yang bagus untuk secara umum lebih memilih kenaikan awalan daripada kenaikan postfix. Sementara kompiler biasanya dapat mengoptimalkan pekerjaan tambahan dari penambahan postfix untuk tipe bawaan, mereka mungkin tidak dapat melakukan hal yang sama untuk tipe yang ditentukan pengguna (yang bisa menjadi sesuatu yang tampak seperti daftar iterator). Setelah Anda terbiasa melakukannya i++
, menjadi sangat sulit untuk diingat untuk melakukan ++i
sebaliknya ketika i
bukan dari tipe built-in (ditambah Anda harus mengubah kode ketika mengubah jenis), jadi lebih baik untuk membuat kebiasaan untuk selalu menggunakan peningkatan awalan, kecuali postfix secara eksplisit diperlukan.
Operator aritmatika biner
Untuk operator aritmatika biner, jangan lupa mematuhi aturan dasar operator ketiga yang berlebihan: Jika Anda memberikan +
, juga memberikan +=
, jika Anda memberikan -
, jangan menghilangkan -=
, dll. Andrew Koenig dikatakan sebagai orang pertama yang mengamati penugasan majemuk. operator dapat digunakan sebagai pangkalan untuk rekanan non-majemuk mereka. Artinya, operator +
diimplementasikan dalam hal +=
, -
diimplementasikan dalam hal -=
dll.
Menurut aturan praktis kami, +
dan teman-temannya harus bukan anggota, sedangkan rekan tugas penugasan mereka ( +=
dll.), Mengubah argumen kiri mereka, harus menjadi anggota. Berikut adalah contoh teladan untuk +=
dan +
; operator aritmatika biner lainnya harus diimplementasikan dengan cara yang sama:
class X {
X& operator+=(const X& rhs)
{
// actual addition of rhs to *this
return *this;
}
};
inline X operator+(X lhs, const X& rhs)
{
lhs += rhs;
return lhs;
}
operator+=
mengembalikan hasilnya per referensi, sementara operator+
mengembalikan salinan hasilnya. Tentu saja, mengembalikan referensi biasanya lebih efisien daripada mengembalikan salinan, tetapi dalam kasus operator+
, tidak ada jalan lain untuk menyalin. Ketika Anda menulis a + b
, Anda mengharapkan hasilnya menjadi nilai baru, itulah sebabnya mengapa operator+
harus mengembalikan nilai baru. 3
Juga perhatikan bahwa operator+
mengambil operan kirinya dengan menyalin daripada dengan referensi const. Alasan untuk ini sama dengan alasan memberi untuk operator=
mengambil argumennya per salinan.
Operator manipulasi bit ~
&
|
^
<<
>>
harus diimplementasikan dengan cara yang sama seperti operator aritmatika. Namun, (kecuali untuk overloading <<
dan >>
untuk output dan input) ada beberapa kasus penggunaan yang wajar untuk overloading ini.
3 Sekali lagi, pelajaran yang bisa diambil dari ini adalah bahwa a += b
, secara umum, lebih efisien daripada a + b
dan harus disukai jika memungkinkan.
Array Berlangganan
Operator subscript array adalah operator biner yang harus diimplementasikan sebagai anggota kelas. Ini digunakan untuk tipe seperti wadah yang memungkinkan akses ke elemen data mereka dengan kunci. Bentuk kanonik dari menyediakan ini adalah ini:
class X {
value_type& operator[](index_type idx);
const value_type& operator[](index_type idx) const;
// ...
};
Kecuali Anda tidak ingin pengguna kelas Anda dapat mengubah elemen data yang dikembalikan oleh operator[]
(dalam hal ini Anda dapat menghilangkan varian non-const), Anda harus selalu menyediakan kedua varian operator.
Jika value_type diketahui merujuk ke tipe bawaan, varian const operator sebaiknya mengembalikan salinan daripada referensi const:
class X {
value_type& operator[](index_type idx);
value_type operator[](index_type idx) const;
// ...
};
Operator untuk Jenis Pointer-like
Untuk menentukan iterator atau smart pointer Anda sendiri, Anda harus membebani operator dereference awalan unary *
dan operator akses anggota penunjuk infix biner ->
:
class my_ptr {
value_type& operator*();
const value_type& operator*() const;
value_type* operator->();
const value_type* operator->() const;
};
Perhatikan bahwa ini juga hampir selalu membutuhkan versi const dan non-const. Untuk ->
operator, jika value_type
dari class
(atau struct
atau union
) tipe, yang lain operator->()
disebut secara rekursif, sampai operator->()
mengembalikan nilai tipe non-kelas.
Operator alamat-unary tidak boleh kelebihan beban.
Untuk operator->*()
melihat pertanyaan ini . Ini jarang digunakan dan karena itu jarang kelebihan beban. Bahkan, iterator sekalipun tidak membebani terlalu banyak.
Lanjutkan ke Operator Konversi