Mengapa fungsi yang dihapus C ++ 11 berpartisipasi dalam resolusi kelebihan beban?

Jawaban:

117

Setengah dari tujuan = deletesintaks adalah untuk dapat mencegah orang memanggil fungsi tertentu dengan parameter tertentu. Ini terutama untuk mencegah konversi implisit dalam skenario tertentu. Untuk melarang kelebihan beban tertentu, itu harus berpartisipasi dalam resolusi kelebihan beban.

Jawaban yang Anda kutip memberi Anda contoh sempurna:

struct onlydouble {
  onlydouble(std::intmax_t) = delete;
  onlydouble(double);
};

Jika deletefungsi dihapus seluruhnya, itu akan membuat = deletesintaksnya setara dengan ini:

struct onlydouble2 {
  onlydouble2(double);
};

Anda bisa melakukan ini:

onlydouble2 val(20);

Ini adalah C ++ legal. Kompilator akan melihat semua konstruktor; tidak satupun dari mereka mengambil tipe integer secara langsung. Tetapi salah satu dari mereka dapat mengambilnya setelah konversi implisit. Jadi itu akan menyebutnya.

onlydouble val(20);

Ini bukan C ++ legal. Kompilator akan melihat semua konstruktor, termasuk yang deleted. Ini akan melihat kecocokan persis, melalui std::intmax_t(yang akan sama persis dengan literal bilangan bulat apa pun). Jadi kompilator akan memilihnya dan kemudian segera mengeluarkan kesalahan, karena memilih deletefungsi d.

= deleteberarti "Saya melarang ini," bukan hanya, "Ini tidak ada." Itu pernyataan yang jauh lebih kuat.

Saya bertanya mengapa standar C ++ mengatakan = delete berarti "Saya melarang ini" alih-alih "ini tidak ada"

Itu karena kita tidak memerlukan tata bahasa khusus untuk mengatakan "ini tidak ada". Kami mendapatkan ini secara implisit hanya dengan tidak menyatakan "ini" tertentu yang dimaksud. "Saya melarang ini" merupakan sebuah konstruksi yang tidak dapat dicapai tanpa tata bahasa khusus. Jadi kita mendapatkan tata bahasa khusus untuk mengatakan "Saya melarang ini" dan bukan yang lain.

Satu-satunya fungsi yang akan Anda peroleh dengan memiliki tata bahasa eksplisit "ini tidak ada" adalah mencegah seseorang untuk menyatakannya di kemudian hari. Dan itu tidak cukup berguna untuk membutuhkan tata bahasanya sendiri.

tidak ada cara lain untuk menyatakan bahwa copy konstruktor tidak ada, dan keberadaannya dapat menyebabkan ambiguitas yang tidak masuk akal.

Konstruktor salinan adalah fungsi anggota khusus. Setiap kelas selalu memiliki konstruktor salinan. Sama seperti mereka selalu memiliki operator penugasan salinan, memindahkan konstruktor, dll.

Fungsi-fungsi ini ada; pertanyaannya hanyalah apakah sah untuk menelepon mereka. Jika Anda mencoba mengatakan itu = deleteberarti mereka tidak ada, maka spesifikasi harus menjelaskan apa artinya fungsi tidak ada. Ini bukan konsep yang ditangani spesifikasi.

Jika Anda mencoba memanggil fungsi yang belum dideklarasikan / didefinisikan, maka compiler akan error. Tetapi ini akan menjadi kesalahan karena pengenal yang tidak ditentukan , bukan karena kesalahan "fungsi tidak ada" (meskipun kompiler Anda melaporkannya seperti itu). Berbagai konstruktor semuanya dipanggil oleh resolusi yang berlebihan, sehingga "keberadaan" mereka ditangani dalam hal itu.

Dalam setiap kasus, ada fungsi yang dideklarasikan melalui pengenal, atau konstruktor / destruktor (juga dideklarasikan melalui pengenal, hanya pengenal tipe). Operator yang kelebihan beban menyembunyikan pengenal di balik gula sintaksis, tetapi pengenal itu masih ada.

Spesifikasi C ++ tidak dapat menangani konsep "fungsi yang tidak ada". Ini dapat menangani ketidakcocokan yang berlebihan. Itu bisa menangani ambiguitas yang berlebihan. Tapi dia tidak tahu tentang apa yang tidak ada di sana. Jadi = deletedidefinisikan dalam istilah "upaya untuk menyebut ini gagal" yang jauh lebih berguna daripada "seolah-olah saya tidak pernah menulis baris ini" yang kurang berguna.

Dan sekali lagi, baca kembali bagian pertama. Anda tidak dapat melakukannya dengan "fungsi tidak ada". Itulah alasan lain mengapa didefinisikan seperti itu: karena salah satu kasus penggunaan utama = deletesintaksis adalah dapat memaksa pengguna untuk menggunakan jenis parameter tertentu, untuk secara eksplisit mentransmisikan, dan sebagainya. Pada dasarnya, untuk menggagalkan konversi tipe implisit.

Saran Anda tidak akan berhasil.

Nicol Bolas
sumber
1
@ Mehrdad: Jika Anda membutuhkan klarifikasi lebih lanjut tentang mengapa = hapus tidak bekerja seperti yang Anda inginkan, Anda perlu lebih jelas pada tepat semantik Anda berpikir = hapus seharusnya. Haruskah itu menjadi "seandainya saya tidak pernah menulis baris ini?" Atau haruskah itu sesuatu yang lain?
Nicol Bolas
1
Tidak, maksud saya yang saya = deletemaksud adalah "anggota ini tidak ada", yang berarti ia tidak dapat berpartisipasi dalam resolusi yang berlebihan.
pengguna541686
6
@Mehrdad: Dan itu kembali ke poin awal saya, itulah mengapa saya mempostingnya: jika = deleteberarti "anggota ini tidak ada", maka contoh pertama yang saya posting tidak akan dapat mencegah orang mengirimkan bilangan bulat ke onlydoublekonstruktor , karena onlydoublekelebihan beban yang dihapus tidak akan ada . Itu tidak akan berpartisipasi dalam resolusi kelebihan beban, dan oleh karena itu tidak akan mencegah Anda meneruskan bilangan bulat. Yang merupakan setengah dari inti = deletesintaks: untuk dapat mengatakan, "Anda tidak dapat meneruskan X secara implisit ke fungsi ini."
Nicol Bolas
3
@Mehrdad: Dengan logika itu, mengapa Anda membutuhkannya =delete? Lagi pula, kita bisa mengatakan "non-copizable" dengan melakukan hal yang persis sama: mendeklarasikan copy constructor / assignment private. Juga, perhatikan bahwa mendeklarasikan sesuatu yang privat tidak membuatnya tidak bisa salah; kode di dalam kelas masih bisa memanggilnya. Jadi tidak sama dengan = delete. Tidak, = deletesintaksis memungkinkan kita melakukan sesuatu yang sebelumnya sangat merepotkan dan tidak dapat dipahami dengan cara yang jauh lebih jelas dan masuk akal.
Nicol Bolas
2
@Mehrdad: Karena yang terakhir dimungkinkan : ini disebut "jangan deklarasikan". Maka itu tidak akan ada. Mengenai mengapa kita membutuhkan sintaks untuk menyembunyikan kelebihan beban daripada menyalahgunakan pribadi ... apakah Anda benar-benar bertanya mengapa kami harus memiliki sarana untuk menyatakan sesuatu secara eksplisit, daripada menyalahgunakan beberapa fitur lain untuk mendapatkan efek yang sama ? Ini lebih jelas bagi siapa pun yang membaca kode apa yang sedang terjadi. Itu membuat kode lebih mudah dipahami oleh pengguna dan membuatnya lebih mudah untuk ditulis, sekaligus memperbaiki masalah dalam penyelesaiannya. Kami juga tidak membutuhkan lambda.
Nicol Bolas
10

Draf Kerja C ++ 2012-11-02 tidak memberikan alasan di balik aturan ini, hanya beberapa contoh

8.4.3 Definisi yang dihapus [dcl.fct.def.delete]
...
3 [ Contoh : Seseorang dapat menerapkan inisialisasi non-default dan inisialisasi non-integral dengan

struct onlydouble {  
  onlydouble() = delete; // OK, but redundant  
  onlydouble(std::intmax_t) = delete;  
  onlydouble(double);  
};  

- contoh akhir ]
[ Contoh : Seseorang dapat mencegah penggunaan kelas dalam ekspresi baru tertentu dengan menggunakan definisi yang dihapus dari operator baru yang dideklarasikan pengguna untuk kelas itu.

struct sometype {  
  void *operator new(std::size_t) = delete;  
  void *operator new[](std::size_t) = delete;  
};  
sometype *p = new sometype; // error, deleted class operator new  
sometype *q = new sometype[3]; // error, deleted class operator new[]  

- contoh akhir ]
[ Contoh : Seseorang dapat membuat kelas tidak dapat disalin, yaitu hanya bergerak, dengan menggunakan definisi yang dihapus dari konstruktor salinan dan operator penugasan salin, dan kemudian memberikan definisi default dari konstruktor pemindahan dan operator penugasan pindah.

struct moveonly {  
  moveonly() = default;  
  moveonly(const moveonly&) = delete;  
  moveonly(moveonly&&) = default;  
  moveonly& operator=(const moveonly&) = delete;  
  moveonly& operator=(moveonly&&) = default;  
  ~moveonly() = default;  
};  
moveonly *p;  
moveonly q(*p); // error, deleted copy constructor  

- contoh akhir ]

Olaf Dietsche
sumber
4
Alasannya tampak sangat jelas dari contoh-contoh tersebut, bukan?
Jesse Good