Pertanyaan Apa aturan dasar dan idiom untuk operator overloading?


Catatan: Jawaban diberikan pesanan tertentu, tetapi karena banyak pengguna yang mengurutkan jawaban berdasarkan suara, daripada waktu yang diberikan, inilah yang indeks jawaban dalam urutan yang paling masuk akal:

(Catatan: Ini dimaksudkan sebagai entri untuk FAQ C ++ Stack Overflow. Jika Anda ingin mengkritik gagasan memberikan FAQ dalam formulir ini, maka posting di meta yang memulai semua ini akan menjadi tempat untuk melakukan itu. Jawaban atas pertanyaan itu dipantau dalam C ++ chatroom, di mana ide FAQ dimulai di tempat pertama, jadi jawaban Anda sangat mungkin dibaca oleh mereka yang menemukan ide tersebut.)  


1841
2017-12-12 12:44


asal


Jawaban:


Operator umum membebani

Sebagian besar pekerjaan di operator overloading adalah kode boiler-plate. Itu tidak mengherankan, karena operator hanyalah gula sintaksis, pekerjaan nyata mereka dapat dilakukan dengan (dan sering diteruskan ke) fungsi-fungsi biasa. Tetapi penting bahwa Anda mendapatkan kode boiler-plate ini dengan benar. Jika Anda gagal, kode operator Anda tidak akan dikompilasi atau kode pengguna Anda tidak akan dikompilasi atau kode pengguna Anda akan berperilaku mengejutkan.

Operator Penugasan

Ada banyak yang harus dikatakan tentang tugas. Namun, sebagian besar sudah dikatakan di FAQ Copy-And-Swap GMan yang terkenal, jadi saya akan melewatkan sebagian besar di sini, hanya mencantumkan operator penugasan yang sempurna untuk referensi:

X& X::operator=(X rhs)
{
  swap(rhs);
  return *this;
}

Operator Bitshift (digunakan untuk Streaming I / O)

Operator bitshift << dan >>, meskipun masih digunakan dalam antarmuka perangkat keras untuk fungsi manipulasi-bit yang diwarisi dari C, telah menjadi lebih umum sebagai operator input dan output aliran yang kelebihan beban di sebagian besar aplikasi. Untuk panduan overloading sebagai operator manipulasi-bit, lihat bagian di bawah ini pada Operator Aritmatika Biner. Untuk menerapkan format kustom Anda sendiri dan menguraikan logika saat objek Anda digunakan dengan iostreams, lanjutkan.

Operator aliran, di antara operator yang paling sering kelebihan beban, adalah operator infiks biner yang sintaksnya menetapkan tidak ada pembatasan apakah mereka harus anggota atau non-anggota. Karena mereka mengubah argumen kiri mereka (mereka mengubah status streaming), mereka harus, sesuai dengan aturan praktis, diimplementasikan sebagai anggota dari jenis operan kiri mereka. Namun, operan kiri mereka adalah aliran dari pustaka standar, dan sementara sebagian besar keluaran sungai dan operator input yang ditentukan oleh pustaka standar memang didefinisikan sebagai anggota kelas aliran, ketika Anda menerapkan operasi input dan output untuk jenis Anda sendiri, Anda tidak dapat mengubah tipe aliran pustaka standar. Itulah mengapa Anda perlu menerapkan operator ini untuk jenis Anda sendiri sebagai fungsi non-anggota. Bentuk kanonik dari keduanya adalah ini:

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>>, pengaturan status sungai secara manual hanya diperlukan saat pembacaan itu sendiri berhasil, tetapi hasilnya tidak seperti yang diharapkan.

Operator panggilan fungsi

Operator panggilan fungsi, yang digunakan untuk membuat objek fungsi, juga dikenal sebagai functor, harus didefinisikan sebagai a anggota berfungsi, sehingga selalu memiliki implisit this argumen fungsi anggota. Selain ini, ia bisa kelebihan beban untuk mengambil sejumlah argumen tambahan, termasuk nol.

Berikut ini contoh sintaks:

class foo {
public:
    // Overloaded call operator
    int operator()(const std::string& y) {
        // ...
    }
};

Pemakaian:

foo f;
int a = f("hello");

Di sepanjang 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 menyalin, lebih baik untuk menyimpan data itu di tempat lain dan memiliki objek fungsi yang merujuk padanya.

Operator perbandingan

Operator perbandingan biner infiks harus, sesuai dengan aturan praktis, diimplementasikan sebagai fungsi non-anggota1. Negosiasi prefiks tanpa kata ! harus (sesuai dengan aturan yang sama) diimplementasikan sebagai fungsi anggota. (tetapi biasanya bukan ide yang baik untuk membebani itu.)

Algoritme perpustakaan standar (mis. std::sort()) dan jenis (mis. std::map) hanya akan selalu berharap operator< hadir. Namun, itu pengguna tipe Anda akan mengharapkan semua operator lain untuk hadirjuga, jadi jika Anda mendefinisikan operator<, pastikan untuk mengikuti aturan dasar ketiga operator yang berlebihan dan juga tentukan semua operator perbandingan boolean lainnya. Cara kanonik untuk menerapkannya 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);}

Hal 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 keduanya untuk melakukan pekerjaan yang sebenarnya.

Sintaks untuk overloading operator boolean biner yang tersisa (||, &&) mengikuti aturan operator pembanding. Namun demikian sangat tidak mungkin bahwa Anda akan menemukan kasus penggunaan yang masuk akal untuk ini2.

1  Seperti semua aturan praktis lainnya, terkadang ada alasan untuk memecahkan yang satu ini juga. Jika demikian, jangan lupa bahwa operan kiri dari operator perbandingan biner, yang berfungsi sebagai anggota *this, perlu constjuga. Jadi operator pembanding yang diimplementasikan sebagai fungsi anggota harus memiliki tanda tangan ini:

bool operator<(const X& rhs) const { /* do actual comparison with *this */ }

(Perhatikan const pada akhirnya.)

2  Perlu dicatat bahwa versi built-in dari || dan && gunakan semantik pintas. Sementara pengguna yang didefinisikan (karena mereka adalah gula sintaksis untuk panggilan metode) tidak menggunakan semantik pintas. Pengguna akan mengharapkan operator ini memiliki semantik pintas, dan kode mereka mungkin bergantung padanya, Oleh karena itu sangat disarankan untuk tidak mendefinisikannya.

Operator Aritmatika

Operator aritmatika yang tidak baku

Operator increment dan decrement datang baik dalam prefix dan postfix flavor. Untuk memberi tahu satu dari yang lain, varian postfix mengambil argumen int boneka tambahan. Jika Anda membebani tambahan atau pengurangan, pastikan untuk selalu menerapkan kedua versi awalan dan postfix. Di sini adalah implementasi kanonik dari penambahan, pengurangan 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 diterapkan dalam hal awalan. Juga perhatikan bahwa postfix melakukan salinan tambahan.2

Overloading unary minus dan plus 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 karena itu kurang efisien untuk digunakan daripada varian prefix. Ini adalah alasan yang bagus untuk secara umum lebih menyukai penambahan awalan pada peningkatan postfix. Sementara kompiler biasanya dapat mengoptimalkan kerja tambahan dari penambahan postfix untuk tipe bawaan, mereka mungkin tidak dapat melakukan hal yang sama untuk jenis yang ditentukan pengguna (yang bisa menjadi sesuatu yang dengan polos terlihat sebagai daftar iterator). Setelah Anda terbiasa melakukannya i++, menjadi sangat sulit untuk diingat ++i sebaliknya kapan i bukan tipe bawaan (ditambah Anda harus mengubah kode saat mengubah tipe), jadi lebih baik untuk selalu menggunakan penambahan awalan, kecuali postfix secara eksplisit diperlukan.

Operator aritmatika biner

Untuk operator aritmatika biner, jangan lupa untuk mematuhi operator aturan dasar ketiga yang kelebihan muatan: Jika Anda menyediakan +, juga menyediakan +=, jika Anda menyediakan -, jangan hilangkan -=, dll. Andrew Koenig dikatakan telah menjadi yang pertama mengamati bahwa operator penugasan senyawa dapat digunakan sebagai basis untuk rekan-rekan non-senyawa mereka. Yaitu, operator + diimplementasikan dalam hal +=, - diimplementasikan dalam hal -= dll.

Menurut aturan praktis kami, + dan teman-temannya harus non-anggota, sementara rekan kerja penugasannya (+= dll.), mengubah argumen kiri mereka, harus menjadi anggota. Berikut ini adalah kode contoh 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 memutar. Ketika Anda menulis a + b, Anda mengharapkan hasilnya menjadi nilai baru, itulah sebabnya operator+ harus mengembalikan nilai baru.3 Juga perhatikan itu operator+ mengambil operan kirinya oleh salinan bukan dengan referensi const. Alasan untuk ini adalah sama dengan alasan memberi operator= mengambil argumennya per copy.

Operator manipulasi sedikit ~  &  |  ^  <<  >> harus dilaksanakan 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 itu a += b adalah, secara umum, lebih efisien daripada a + b dan sebaiknya dipilih jika memungkinkan.

Subscripting Array

Operator subscript array adalah operator biner yang harus dilaksanakan sebagai anggota kelas. Ini digunakan untuk jenis yang mirip kontainer yang memungkinkan akses ke elemen data mereka dengan kunci. Bentuk kanonik 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 untuk 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 harus mengembalikan salinan alih-alih referensi const.

Operator untuk Jenis Penunjuk

Untuk mendefinisikan iterator Anda sendiri atau pointer pintar, Anda harus membebani operator dereference prefix yang tidak ada * dan operator akses anggota penunjuk infix pointer ->:

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 baik versi const dan non-const. Untuk -> operator, jika value_type adalah dari class (atau struct atau union) ketik, yang lain operator->() disebut secara rekursif, hingga operator->() mengembalikan nilai tipe non-kelas.

Alamat operator yang tidak semestinya tidak boleh kelebihan beban.

Untuk operator->*() Lihat pertanyaan ini. Ini jarang digunakan dan dengan demikian jarang yang pernah kelebihan beban. Bahkan, iterator pun tidak membebani itu.


Lanjutkan ke Operator Konversi


896
2017-12-12 12:47



Tiga Aturan Dasar Operator Overloading di C ++

Ketika datang ke operator overloading di C ++, ada tiga aturan dasar yang harus Anda ikuti. Seperti halnya semua aturan seperti itu, memang ada pengecualian. Kadang-kadang orang telah menyimpang dari mereka dan hasilnya bukanlah kode yang buruk, tetapi penyimpangan positif seperti itu sedikit dan jauh di antara keduanya. Setidak-tidaknya, 99 dari 100 penyimpangan seperti itu yang saya lihat tidak dapat dibenarkan. Namun, mungkin saja ada 999 dari 1.000. Jadi Anda sebaiknya tetap mengikuti aturan berikut.

  1. Kapanpun arti dari seorang operator tidak jelas dan tidak terbantahkan, itu tidak boleh kelebihan beban.  Sebaliknya, berikan fungsi dengan nama yang dipilih dengan baik.
    Pada dasarnya, aturan pertama dan utama untuk operator overloading, pada intinya, mengatakan: Jangan lakukan itu. Itu mungkin tampak aneh, karena ada banyak hal yang diketahui tentang operator yang kelebihan beban dan begitu banyak artikel, bab buku, dan teks-teks lainnya berurusan dengan semua ini. Tetapi meskipun bukti yang tampaknya jelas ini, hanya ada sedikit kasus di mana operator overloading sesuai. Alasannya adalah bahwa sebenarnya sulit untuk memahami semantik di balik penerapan operator kecuali penggunaan operator dalam domain aplikasi terkenal dan tidak terbantahkan. Bertentangan dengan kepercayaan populer, ini hampir tidak pernah terjadi.

  2. Selalu patuh pada semantik terkenal operator.
    C + + tidak menimbulkan batasan pada semantik operator yang kelebihan beban. Compiler Anda dengan senang hati akan menerima kode yang mengimplementasikan biner + operator dikurangi dari operan kanannya. Namun, pengguna operator seperti itu tidak akan pernah menduga ekspresi a + b untuk dikurangi a dari b. Tentu saja, ini mengandaikan bahwa semantik operator dalam domain aplikasi tidak terbantahkan.

  3. Selalu sediakan semua dari serangkaian operasi terkait.
    Operator saling terkait satu sama laindan operasi lainnya. Jika tipe Anda mendukung a + b, pengguna akan berharap untuk dapat menelepon a += bjuga. Jika mendukung penambahan awalan ++a, mereka akan berharap a++ untuk bekerja juga. Jika mereka dapat memeriksa apakah a < b, mereka pasti akan berharap juga untuk dapat memeriksa apakah a > b. Jika mereka dapat menyalin-membangun tipe Anda, mereka mengharapkan tugas untuk bekerja juga.


Lanjutkan ke Keputusan antara Anggota dan Non-anggota.


440
2017-12-12 12:45



Sintaks umum operator overloading di C ++

Anda tidak dapat mengubah arti operator untuk tipe bawaan di C ++, operator hanya dapat kelebihan beban untuk jenis yang ditentukan pengguna1. Artinya, setidaknya satu dari operand harus dari tipe yang ditentukan pengguna. Seperti fungsi-fungsi yang kelebihan beban lainnya, operator dapat kelebihan beban untuk satu set parameter tertentu hanya sekali.

Tidak semua operator dapat kelebihan muatan di C ++. Di antara operator yang tidak bisa kelebihan beban adalah: .  ::  sizeof  typeid  .* dan satu-satunya operator terner di C ++, ?: 

Di antara operator yang dapat kelebihan beban di C ++ adalah ini:

  • operator aritmatika: +  -  *  /  % dan +=  -=  *=  /=  %= (semua infiks biner); +  - (awalan awalan); ++  -- (awalan dan postfix yang tidak asli)
  • sedikit manipulasi: &  |  ^  <<  >> dan &=  |=  ^=  <<=  >>= (semua infiks biner); ~ (awalan awalan)
  • aljabar Boolean: ==  !=  <  >  <=  >=  ||  && (semua infiks biner); ! (awalan awalan)
  • manajemen memori: new  new[]  delete  delete[]
  • operator konversi implisit
  • varia: =  []  ->  ->*  ,  (semua infiks biner); *  & (semua awalan unary) () (fungsi panggilan, infix n-ary)

Namun, faktanya bahwa Anda bisa membebani semua ini bukan berarti Anda harus lakukan itu. Lihat aturan dasar operator overloading.

Di C ++, operator kelebihan beban dalam bentuk berfungsi dengan nama khusus. Seperti halnya fungsi lain, operator yang kelebihan muatan umumnya dapat diimplementasikan baik sebagai a fungsi anggota dari tipe operan kiri mereka atau sebagai fungsi non-anggota. Apakah Anda bebas memilih atau terikat untuk menggunakan salah satunya tergantung pada beberapa kriteria.2 Operator yang tidak baik @3, diterapkan ke objek x, dipanggil sebagai operator@(x) atau sebagai x.operator@(). Operator infiks biner @, diterapkan pada objek x dan y, disebut sebagai operator@(x,y) atau sebagai x.operator@(y).4 

Operator yang diimplementasikan sebagai fungsi non-anggota terkadang adalah teman dari tipe operan mereka.

1  Istilah "ditentukan pengguna" mungkin sedikit menyesatkan. C ++ membuat perbedaan antara tipe built-in dan tipe yang ditentukan pengguna. Untuk yang pertama milik misalnya int, char, dan double; untuk yang terakhir milik semua tipe struct, class, union, dan enum, termasuk yang berasal dari pustaka standar, meskipun mereka tidak, dengan demikian, didefinisikan oleh pengguna.

2  Ini tercakup dalam bagian selanjutnya dari FAQ ini.

3  Itu @ bukan operator yang valid di C ++ yang mengapa saya menggunakannya sebagai placeholder.

4  Operator terner satu-satunya di C ++ tidak dapat kelebihan beban dan satu-satunya operator n-ary harus selalu diimplementasikan sebagai fungsi anggota.


Lanjutkan ke Tiga Aturan Dasar Operator Overloading di C ++.


229
2017-12-12 12:46



Keputusan antara Anggota dan Non-anggota

Operator biner = (tugas), [] (langganan larik), -> (akses anggota), serta n-ary ()(panggilan fungsi) operator, harus selalu diimplementasikan sebagai fungsi anggota, karena sintaks bahasa mengharuskannya.

Operator lain dapat diimplementasikan baik sebagai anggota atau bukan anggota. Beberapa dari mereka, bagaimanapun, biasanya harus diimplementasikan sebagai fungsi non-anggota, karena operan kiri mereka tidak dapat dimodifikasi oleh Anda. Yang paling menonjol adalah operator input dan output << dan >>, yang operand kirinya adalah kelas arus dari pustaka standar yang tidak dapat Anda ubah.

Untuk semua operator di mana Anda harus memilih untuk mengimplementasikannya sebagai fungsi anggota atau fungsi non-anggota, gunakan aturan praktis berikut ini untuk memutuskan:

  1. Jika itu a operator tidak senonoh, terapkan sebagai anggota fungsi.
  2. Jika operator biner memperlakukan kedua operand sama (itu membuat mereka tidak berubah), menerapkan operator ini sebagai bukan anggota fungsi.
  3. Jika operator biner melakukannya tidak perlakukan kedua operandnya sama (biasanya akan mengubah operan kirinya), mungkin berguna untuk membuatnya a anggota fungsi dari tipe operan kirinya, jika harus mengakses bagian pribadi operan.

Tentu saja, seperti semua aturan praktis, ada pengecualian. Jika Anda memiliki tipe

enum Month {Jan, Feb, ..., Nov, Dec}

dan Anda ingin membebani operator inkrement dan decrement untuk itu, Anda tidak dapat melakukan ini sebagai fungsi anggota, karena di C ++, enum jenis tidak dapat memiliki fungsi anggota. Jadi Anda harus membebani itu sebagai fungsi gratis. Dan operator<() untuk templat kelas yang disarangkan dalam template kelas jauh lebih mudah untuk ditulis dan dibaca ketika selesai sebagai fungsi anggota sebaris dalam definisi kelas. Tapi ini memang pengecualian langka.

(Namun, jika Anda membuat pengecualian, jangan lupa masalah const-ness untuk operand itu, untuk fungsi anggota, menjadi tersirat this argumen. Jika operator sebagai fungsi non-anggota akan mengambil argumen paling kiri sebagai a const referensi, operator yang sama sebagai fungsi anggota harus memiliki const pada akhirnya untuk membuatnya *this Sebuah const referensi.)


Lanjutkan ke Operator umum membebani.


211
2017-12-12 12:49



Operator Konversi (juga dikenal sebagai Konversi Buatan Pengguna)

Di C ++ Anda dapat membuat operator konversi, operator yang memungkinkan compiler untuk mengkonversi antara jenis Anda dan jenis yang ditentukan lainnya. Ada dua jenis operator konversi, yang implisit dan eksplisit.

Operator Konversi Tersirat (C ++ 98 / C ++ 03 dan C ++ 11)

Operator konversi implisit memungkinkan compiler untuk mengkonversi secara implisit (seperti konversi antara int dan long) nilai dari jenis yang ditentukan pengguna untuk beberapa jenis lainnya.

Berikut ini adalah kelas sederhana dengan operator konversi implisit:

class my_string {
public:
  operator const char*() const {return data_;} // This is the conversion operator
private:
  const char* data_;
};

Operator konversi tersirat, seperti konstruktor satu argumen, adalah konversi yang ditentukan pengguna. Compiler akan memberikan satu konversi yang ditentukan pengguna saat mencoba mencocokkan panggilan ke fungsi yang kelebihan beban.

void f(const char*);

my_string str;
f(str); // same as f( str.operator const char*() )

Pada awalnya ini tampaknya sangat membantu, tetapi masalah dengan ini adalah bahwa konversi implisit bahkan menendang ketika tidak diharapkan. Dalam kode berikut, void f(const char*)akan dipanggil karena my_string() bukan sebuah lvalue, jadi yang pertama tidak cocok:

void f(my_string&);
void f(const char*);

f(my_string());

Pemula mudah mendapatkan programmer C ++ yang salah dan bahkan berpengalaman ini kadang-kadang terkejut karena compiler mengambil kelebihan beban yang tidak mereka curigai. Masalah-masalah ini dapat dimitigasi oleh operator konversi eksplisit.

Operator Konversi Eksplisit (C ++ 11)

Tidak seperti operator konversi implisit, operator konversi eksplisit tidak akan pernah menendang ketika Anda tidak mengharapkannya. Berikut ini adalah kelas sederhana dengan operator konversi eksplisit:

class my_string {
public:
  explicit operator const char*() const {return data_;}
private:
  const char* data_;
};

Perhatikan explicit. Sekarang ketika Anda mencoba untuk mengeksekusi kode tak terduga dari operator konversi implisit, Anda mendapatkan kesalahan kompiler:

prog.cpp: Dalam fungsi ‘int main ()’:
prog.cpp: 15: 18: kesalahan: tidak ada fungsi yang cocok untuk panggilan ke ‘f (my_string)’
prog.cpp: 15: 18: catatan: kandidat adalah:
prog.cpp: 11: 10: catatan: void f (my_string &)
prog.cpp: 11: 10: catatan: tidak ada konversi yang diketahui untuk argumen 1 dari ‘my_string’ ke ‘my_string &’
prog.cpp: 12: 10: note: void f (const char *)
prog.cpp: 12: 10: catatan: tidak ada konversi yang diketahui untuk argumen 1 dari ‘my_string’ ke ‘const char *’

Untuk memanggil operator cor eksplisit, Anda harus menggunakan static_cast, pemeran C-style, atau pemeran gaya konstruktor (mis. T(value) ).

Namun, ada satu pengecualian untuk ini: Kompilator dibolehkan secara implisit dikonversi menjadi bool. Selain itu, compiler tidak diperbolehkan melakukan konversi implisit lain setelah dikonversi menjadi bool (kompilator diperbolehkan melakukan 2 konversi implisit sekaligus, tetapi hanya 1 konversi yang ditentukan pengguna pada max).

Karena compiler tidak akan mentransmisikan "masa lalu" bool, operator konversi eksplisit sekarang menghapus kebutuhan akan Idiot Bool yang aman. Misalnya, pointer pintar sebelum C ++ 11 menggunakan idiom Safe Bool untuk mencegah konversi ke tipe integral. Dalam C ++ 11, pointer pintar menggunakan operator eksplisit sebagai gantinya karena compiler tidak dibolehkan secara implisit mengkonversi ke tipe integral setelah secara eksplisit mengkonversi suatu tipe ke bool.

Lanjutkan ke Overloading new dan delete.


143
2018-05-17 18:32



Overloading new dan delete

catatan: Ini hanya berhubungan dengan sintaksis overloading new dan delete, bukan dengan pelaksanaan operator yang kelebihan beban tersebut. Saya pikir itu semantik overloading new dan delete berhak mendapatkan FAQ mereka sendiri, dalam topik operator overloading saya tidak pernah bisa melakukannya keadilan.

Dasar-dasar

Di C ++, saat Anda menulis a ekspresi baru seperti new T(arg) dua hal terjadi ketika ungkapan ini dievaluasi: Pertama operator new dipanggil untuk mendapatkan memori mentah, dan kemudian konstruktor yang sesuai T dipanggil untuk mengubah memori mentah ini menjadi objek yang valid. Demikian pula, ketika Anda menghapus suatu objek, pertama destruktornya dipanggil, dan kemudian memori dikembalikan ke operator delete.
C ++ memungkinkan Anda untuk mengatur kedua operasi ini: manajemen memori dan konstruksi / penghancuran objek pada memori yang dialokasikan. Yang terakhir dilakukan dengan menulis konstruktor dan destruktor untuk kelas. Penyempurnaan manajemen memori dilakukan dengan menulis sendiri operator new dan operator delete.

Yang pertama dari aturan dasar operator overloading - jangan lakukan itu - Berlaku terutama untuk overloading new dan delete. Hampir satu-satunya alasan untuk membebani operator ini adalah masalah kinerja dan kendala memori, dan dalam banyak kasus, tindakan lain, seperti perubahan pada algoritme digunakan, akan memberikan banyak rasio biaya / keuntungan yang lebih tinggi daripada mencoba men-tweak manajemen memori.

Pustaka standar C ++ dilengkapi dengan satu set yang telah ditentukan sebelumnya new dan delete operator. Yang paling penting adalah ini:

void* operator new(std::size_t) throw(std::bad_alloc); 
void  operator delete(void*) throw(); 
void* operator new[](std::size_t) throw(std::bad_alloc); 
void  operator delete[](void*) throw(); 

Dua yang pertama mengalokasikan / membatalkan alokasi memori untuk suatu objek, dua yang terakhir untuk suatu array objek. Jika Anda memberikan versi Anda sendiri, mereka akan melakukannya tidak berlebihan, tetapi ganti yang dari perpustakaan standar.
Jika Anda membebani operator new, Anda harus selalu membebani pencocokan operator delete, bahkan jika Anda tidak pernah ingin menyebutnya. Alasannya adalah bahwa, jika konstruktor melempar selama evaluasi ekspresi baru, sistem run-time akan mengembalikan memori ke operator delete cocok dengan operator new yang dipanggil untuk mengalokasikan memori untuk membuat objek. Jika Anda tidak memberikan yang cocok operator delete, yang baku disebut, yang hampir selalu salah.
Jika Anda membebani new dan delete, Anda harus mempertimbangkan overloading varian larik juga.

Penempatan new

C ++ memungkinkan operator baru dan menghapus untuk mengambil argumen tambahan.
Disebut penempatan baru memungkinkan Anda membuat objek di alamat tertentu yang diteruskan ke:

class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{ 
  X* p = new(buffer) X(/*...*/);
  // ... 
  p->~X(); // call destructor 
} 

Perpustakaan standar dilengkapi dengan kelebihan yang sesuai dari operator baru dan hapus untuk ini:

void* operator new(std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete(void* p,void*) throw(); 
void* operator new[](std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete[](void* p,void*) throw(); 

Perhatikan bahwa, dalam kode contoh untuk penempatan baru yang diberikan di atas, operator delete tidak pernah dipanggil, kecuali konstruktor X melempar pengecualian.

Anda juga bisa membebani new dan delete dengan argumen lain. Seperti halnya argumen tambahan untuk penempatan baru, argumen ini juga tercantum dalam tanda kurung setelah kata kunci new. Hanya untuk alasan historis, varian tersebut sering juga disebut penempatan baru, bahkan jika argumen mereka bukan untuk menempatkan objek pada alamat tertentu.

Kelas baru dan hapus

Paling umum Anda akan ingin menyempurnakan manajemen memori karena pengukuran telah menunjukkan bahwa contoh kelas tertentu, atau sekelompok kelas terkait, dibuat dan dihancurkan sering dan bahwa manajemen memori default dari sistem run-time, disetel untuk kinerja umum, berhubungan secara tidak efisien dalam kasus spesifik ini. Untuk meningkatkan ini, Anda dapat membebani baru dan menghapus untuk kelas tertentu:

class my_class { 
  public: 
    // ... 
    void* operator new();
    void  operator delete(void*,std::size_t);
    void* operator new[](size_t);
    void  operator delete[](void*,std::size_t);
    // ... 
}; 

Kelebihan beban dengan demikian, baru dan menghapus berperilaku seperti fungsi anggota statis. Untuk objek my_class, yang std::size_t argumen akan selalu sizeof(my_class). Namun, operator ini juga disebut untuk objek yang dialokasikan secara dinamis kelas turunan, dalam hal ini mungkin lebih besar dari itu.

Global baru dan hapus

Untuk membebani global baru dan menghapus, cukup ganti operator perpustakaan standar yang telah ditetapkan sebelumnya dengan milik kita sendiri. Namun, ini jarang sekali perlu dilakukan.


130
2017-12-12 13:07



Kenapa tidak bisa operator<< berfungsi untuk mengalirkan objek ke std::cout atau ke file menjadi fungsi anggota?

Katakanlah Anda memiliki:

struct Foo
{
   int a;
   double b;

   std::ostream& operator<<(std::ostream& out) const
   {
      return out << a << " " << b;
   }
};

Karena itu, Anda tidak dapat menggunakan:

Foo f = {10, 20.0};
std::cout << f;

Sejak operator<< kelebihan beban sebagai fungsi anggota Foo, LHS operator harus berupa Foo obyek. Yang berarti, Anda akan diminta untuk menggunakan:

Foo f = {10, 20.0};
f << std::cout

yang sangat tidak intuitif.

Jika Anda mendefinisikannya sebagai fungsi non-anggota,

struct Foo
{
   int a;
   double b;
};

std::ostream& operator<<(std::ostream& out, Foo const& f)
{
   return out << f.a << " " << f.b;
}

Anda akan dapat menggunakan:

Foo f = {10, 20.0};
std::cout << f;

yang sangat intuitif.


29
2018-01-22 19:00