Mengapa C ++ tidak memungkinkan Anda mengambil alamat konstruktor?

14

Apakah ada alasan khusus bahwa ini akan memecah bahasa secara konseptual atau alasan tertentu bahwa ini secara teknis tidak mungkin dilakukan dalam beberapa kasus?

Penggunaannya akan dengan operator baru.

Sunting: Saya akan berhenti berharap mendapatkan "operator baru" dan "operator baru" saya secara langsung.

Inti pertanyaannya adalah: mengapa konstruktor istimewa ? Ingatlah selalu bahwa spesifikasi bahasa memberi tahu kita apa yang legal, tetapi belum tentu moral. Apa yang legal biasanya diinformasikan oleh apa yang secara logis konsisten dengan bagian bahasa yang lain, apa yang sederhana dan ringkas, dan apa yang layak untuk diimplementasikan oleh kompiler. Alasan yang mungkin dari komite standar dalam menimbang faktor-faktor ini adalah disengaja dan menarik - karena itu pertanyaannya.

Praxeolitic
sumber
Ini tidak akan menjadi masalah mengambil alamat konstruktor, tetapi bisa melewati tipe. Template dapat melakukannya.
Euforia
Bagaimana jika Anda memiliki templat fungsi yang ingin Anda buat objek menggunakan konstruktor yang akan ditentukan sebagai argumen ke fungsi?
Praxeolitic
1
Akan ada alternatif untuk contoh apa pun yang dapat saya pikirkan, tetapi tetap saja, mengapa konstruktor harus istimewa? Ada banyak hal yang kemungkinan besar tidak akan Anda gunakan dalam kebanyakan bahasa pemrograman tetapi kasus-kasus khusus seperti ini biasanya datang dengan justifikasi.
Praxeolitic
1
@RobertHarvey Pertanyaan muncul pada saya ketika saya hendak mengetik kelas pabrik.
Praxeolitic
1
Saya ingin tahu apakah C ++ 11 std::make_uniquedan std::make_shareddapat secara memadai memecahkan motivasi praktis yang mendasari untuk pertanyaan ini. Ini adalah metode templat, yang berarti seseorang perlu menangkap argumen input ke konstruktor, dan kemudian meneruskannya ke konstruktor yang sebenarnya.
rwong

Jawaban:

10

Fungsi pointer-ke-anggota hanya masuk akal jika Anda memiliki lebih dari satu fungsi anggota dengan tanda tangan yang sama - jika tidak, hanya akan ada satu nilai yang mungkin untuk pointer Anda. Tetapi itu tidak mungkin untuk konstruktor, karena dalam C ++ konstruktor yang berbeda dari kelas yang sama harus memiliki tanda tangan yang berbeda.

Alternatif untuk Stroustrup adalah memilih sintaks untuk C ++ di mana konstruktor dapat memiliki nama yang berbeda dari nama kelas - tetapi itu akan mencegah beberapa aspek yang sangat elegan dari sintaks ctor yang ada dan membuat bahasa lebih rumit. Bagi saya yang terlihat seperti harga tinggi hanya untuk memungkinkan fitur yang jarang diperlukan yang dapat dengan mudah disimulasikan dengan "outsourcing" inisialisasi objek dari ctor ke initfungsi yang berbeda (fungsi anggota normal yang dapat menjadi pointer-ke-anggota dapat dibuat).

Doc Brown
sumber
2
Tetap saja, mengapa dicegah memcpy(buffer, (&std::string)(int, char), size)? (Mungkin sangat tidak halal, tapi ini C ++.)
Thomas Eding
3
maaf, tetapi apa yang Anda tulis tidak masuk akal. Saya melihat tidak ada yang salah dengan memiliki pointer ke anggota menunjuk ke sebuah konstruktor. juga, Kedengarannya seperti Anda mengutip sesuatu, tanpa tautan ke sumber.
BЈовић
1
@ThomasEding: apa yang sebenarnya Anda harapkan dari pernyataan itu? Menyalin kode rakitan string ctor di suatu tempat? Bagaimana "ukuran" ditentukan (bahkan jika Anda mencoba sesuatu yang setara untuk fungsi anggota standar)?
Doc Brown
Saya berharap itu melakukan hal yang sama dengan yang diberikan alamat pointer fungsi gratis memcpy(buffer, strlen, size). Agaknya itu akan menyalin majelis, tetapi siapa yang tahu. Apakah kode itu dapat dipanggil atau tidak tanpa crash akan membutuhkan pengetahuan tentang kompiler yang Anda gunakan. Hal yang sama berlaku untuk menentukan ukuran. Ini akan sangat bergantung pada platform, tetapi banyak konstruksi C ++ non-portabel digunakan dalam kode produksi. Saya tidak melihat alasan untuk melarangnya.
Thomas Eding
@ThomasEding: Compiler C ++ yang sesuai diharapkan memberikan diagnostik ketika mencoba mengakses pointer fungsi seolah-olah itu adalah pointer data. Kompiler C ++ yang tidak sesuai bisa melakukan apa saja, tetapi mereka juga dapat menyediakan cara non-c ++ untuk mengakses konstruktor. Itu bukan alasan untuk menambahkan fitur ke C ++ yang tidak memiliki kegunaan dalam kode yang sesuai.
Bart van Ingen Schenau
5

Konstruktor adalah fungsi yang Anda panggil saat objek belum ada, jadi itu tidak bisa menjadi fungsi anggota. Itu bisa statis.

Konstruktor sebenarnya dipanggil dengan pointer ini, setelah memori telah dialokasikan tetapi sebelum sepenuhnya diinisialisasi. Akibatnya seorang konstruktor memiliki sejumlah fitur istimewa.

Jika Anda memiliki pointer ke constructor itu harus menjadi pointer statis, sesuatu seperti fungsi pabrik, atau pointer khusus ke sesuatu yang akan dipanggil segera setelah alokasi memori. Itu tidak bisa menjadi fungsi anggota biasa dan masih berfungsi sebagai konstruktor.

Satu-satunya tujuan berguna yang terlintas dalam pikiran adalah jenis pointer khusus yang dapat diteruskan ke operator baru untuk memungkinkannya untuk tidak langsung pada konstruktor mana yang digunakan. Saya kira itu bisa berguna, tetapi akan memerlukan sintaks baru yang signifikan dan mungkin jawabannya adalah: mereka memikirkannya dan itu tidak sepadan dengan usaha.

Jika Anda hanya ingin refactor kode inisialisasi umum maka fungsi memori biasa biasanya merupakan jawaban yang cukup, dan Anda bisa mendapatkan pointer ke salah satu dari itu.

david.pfx
sumber
Ini sepertinya jawaban yang paling benar. Saya ingat sebuah artikel dari bertahun-tahun yang lalu mengenai operator baru dan cara kerja internal "operator baru". operator baru () mengalokasikan ruang. Operator baru memanggil konstruktor dengan ruang yang dialokasikan itu. Mengambil alamat konstruktor adalah "istimewa" karena memanggil konstruktor membutuhkan ruang. Akses untuk memanggil konstruktor seperti ini adalah dengan penempatan baru.
Bill Door
1
Kata "ada" mengaburkan detail bahwa suatu objek dapat memiliki alamat dan mengalokasikan memori tetapi tidak diinisialisasi. Pada fungsi anggota atau tidak, saya pikir mendapatkan pointer ini membuat fungsi menjadi fungsi anggota karena itu jelas terkait dengan instance objek (bahkan jika tidak diinisialisasi). Yang mengatakan, jawabannya menimbulkan poin yang baik: konstruktor adalah satu-satunya fungsi anggota yang dapat dipanggil pada objek yang tidak diinisialisasi.
Praxeolitic
Nevermind, tampaknya mereka memiliki penunjukan "fungsi anggota khusus". Klausa 12 dari standar C ++ 11: "Konstruktor default (12.1), salin konstruktor dan operator penugasan salin (12.8), memindahkan konstruktor dan memindahkan penugasan operator (12.8), dan destruktor (12.4) adalah fungsi anggota khusus ."
Praxeolitic
Dan 12.1: "Sebuah konstruktor tidak boleh virtual (10.3) atau statis (9.4)." (Penekanan saya)
Praxeolitic
1
Faktanya adalah bahwa jika Anda mengkompilasi dengan simbol debug dan mencari jejak stack, sebenarnya ada pointer ke konstruktor. Apa yang saya tidak pernah bisa adalah menemukan sintaks untuk mendapatkan pointer ini ( &A::Atidak berfungsi di salah satu kompiler yang saya coba.)
alfC
-3

Ini karena mereka bukan tipe kembali konstruktor dan Anda tidak memesan ruang untuk konstruktor dalam memori. Seperti yang Anda lakukan dalam kasus variabel selama deklarasi. Sebagai contoh: jika Anda menulis variabel X sederhana maka kompiler akan menghasilkan kesalahan karena kompiler tidak akan mengerti arti ini. Tetapi ketika Anda menulis Int x; Kemudian kompiler mengetahui bahwa itu tipe variabel data, sehingga akan disediakan ruang untuk variabel.

Kesimpulan: - jadi kesimpulannya adalah karena pengecualian tipe pengembalian tidak akan mendapatkan alamat dalam memori.

mewah Setia
sumber
1
Kode dalam konstruktor harus memiliki alamat di memori karena harus ada di suatu tempat. Tidak perlu menyimpan ruang untuk itu di stack tetapi harus ada di suatu tempat di memori. Anda dapat mengambil alamat fungsi yang tidak mengembalikan nilai. (void)(*fptr)()mendeklarasikan pointer ke fungsi tanpa nilai balik.
Praxeolitic
2
Anda melewatkan inti pertanyaan - pos asli bertanya tentang mengambil alamat kode untuk konstruktor, bukan hasil yang diberikan konstruktor. Selain itu, di forum ini, silakan gunakan kata-kata lengkap: "u" bukan pengganti yang dapat diterima untuk "Anda".
BobDalgleish
Mr praxeolitic, saya pikir jika kita tidak menyebutkan jenis kembali maka kompiler tidak akan menetapkan lokasi memori tertentu untuk ctor dan lokasinya diatur secara internal .... Bisakah kita mengambil alamat segala hal dalam c ++ yang tidak diberikan oleh penyusun? Jika saya salah maka tolong perbaiki saya dengan jawaban yang benar
lih. Goyal
Dan juga beri tahu saya tentang variabel referensi. Bisakah kita mengambil alamat variabel referensi? Jika tidak maka alamat apa printf ("% u", & (& (j))); sedang mencetak jika & j = x di mana x = 10? Karena alamat yang dicetak oleh printf dan alamat x tidak sama
Goyal
-4

Saya akan menebak:

Konstruktor dan destruktor C ++ sama sekali tidak berfungsi: semuanya makro. Mereka masuk ke dalam ruang lingkup di mana objek dibuat, dan ruang lingkup di mana objek dihancurkan. Pada gilirannya, tidak ada konstruktor atau destruktor, objek hanya IS.

Sebenarnya, saya pikir fungsi-fungsi lain di dalam kelas juga bukan fungsi, tetapi fungsi sebaris yang DONT dapatkan sebaris karena Anda mengambil alamatnya (kompiler menyadari Anda ke dalamnya dan tidak inline atau inline kode ke dalam fungsi dan mengoptimalkan fungsi itu) dan pada gilirannya fungsi tersebut tampaknya "masih ada", meskipun tidak jika Anda belum mengatasinya.

Tabel virtual "objek" C ++ tidak seperti objek JavaScript, di mana Anda bisa mendapatkan konstruktornya dan membuat objek darinya saat runtime via new XMLHttpRequest.constructor, melainkan kumpulan pointer ke fungsi anonim yang bertindak sebagai alat untuk berinteraksi dengan objek ini , tidak termasuk kemampuan untuk membuat objek. Dan itu bahkan tidak masuk akal untuk "menghapus" objek, karena itu seperti mencoba untuk menghapus struct, Anda tidak bisa: itu hanya label tumpukan, hanya menulis kepadanya sesuka Anda di bawah label lain: Anda bebas untuk gunakan kelas sebagai 4 bilangan bulat:

/* i imagine this string gets compiled into a struct, one of which's members happens to be a const char * which is initialized to exactly your string: no function calls are made during construction. */
std::string a = "hello, world";
int *myInt = (int *)(*((void **)&a));
myInt[0] = 3;
myInt[1] = 9;
myInt[2] = 20;
myInt[3] = 300;

Tidak ada kebocoran memori, tidak ada masalah, kecuali Anda secara efektif membuang banyak ruang stack yang disediakan untuk antarmuka objek dan string, tetapi itu tidak akan merusak program Anda (selama Anda tidak mencoba menggunakannya) sebagai string lagi).

Sebenarnya, jika asumsi saya sebelumnya benar: biaya lengkap string hanyalah biaya menyimpan 32 byte ini dan ruang string konstan: fungsi hanya digunakan pada waktu kompilasi, dan mungkin juga bisa digarisbawahi dan dibuang setelah objek dibuat dan digunakan (Seolah-olah Anda bekerja dengan struct dan hanya merujuk secara langsung tanpa panggilan fungsi, tentu ada panggilan duplikat alih-alih fungsi melompat, tetapi ini biasanya lebih cepat dan menggunakan lebih sedikit ruang). Intinya, setiap kali Anda memanggil fungsi apa pun, kompiler hanya mengganti panggilan itu dengan instruksi untuk benar-benar melakukannya, dengan pengecualian yang telah ditetapkan oleh perancang bahasa.

Ringkasan: objek C ++ tidak tahu apa itu; semua alat untuk berinteraksi dengan mereka diuraikan secara statis, dan hilang saat runtime. Ini membuat bekerja dengan kelas seefisien mengisi struct dengan data, dan langsung bekerja dengan data itu tanpa memanggil fungsi apa pun (fungsi-fungsi ini diuraikan).

Ini benar-benar berbeda dari pendekatan COM / ObjectiveC serta javascript, yang mempertahankan jenis informasi secara dinamis, dengan biaya runtime overhead, manajemen memori, panggilan konstruksi, karena kompiler tidak dapat membuang informasi ini: diperlukan untuk pengiriman dinamis. Ini pada gilirannya memberi kita kemampuan untuk "Bicara" ke program kami saat runtime, dan mengembangkannya saat sedang berjalan dengan memiliki komponen yang dapat dipantulkan.

Dmitry
sumber
2
maaf, tetapi beberapa bagian dari "jawaban" ini salah atau menyesatkan. Sayangnya, ruang komentar terlalu kecil untuk mendaftar semuanya (sebagian besar metode tidak akan dimasukkan, ini akan mencegah pengiriman virtual dan mengasapi biner; bahkan jika digarisbawahi, mungkin ada salinan yang dapat dialamatkan di suatu tempat yang dapat diakses; contoh kode tidak relevan yang di kasus terburuk merusak tumpukan Anda dan dalam kasus terbaik tidak sesuai dengan asumsi Anda; ...)
hoffmale
Jawabannya naif, saya hanya ingin mengungkapkan dugaan saya mengapa konstruktor / destruktor tidak dapat dirujuk. Saya setuju bahwa dalam kasus kelas virtual, vtable harus bertahan, dan kode addressable harus ada dalam memori sehingga vtable dapat merujuknya. Namun, kelas-kelas yang tidak mengimplementasikan kelas virtual, tampaknya akan diuraikan, seperti dalam kasus std :: string. Tidak semuanya diuraikan, tetapi hal-hal yang tampaknya tidak secara minimal dimasukkan ke dalam blok kode "anonim" di suatu tempat di memori. Juga, bagaimana kode merusak tumpukan? Tentu saja kami kehilangan talinya, tetapi yang kami lakukan hanyalah menafsirkannya kembali.
Dmitry
Kerusakan memori terjadi dalam program komputer ketika konten lokasi memori tidak sengaja diubah. Program ini melakukannya dengan sengaja dan tidak mencoba menggunakan string itu lagi, sehingga tidak ada korupsi, hanya membuang ruang stack. Tapi ya, invarian string tidak lagi dipertahankan, itu mengacaukan ruang lingkup (pada akhirnya, stack dipulihkan).
Dmitry
tergantung pada implementasi string Anda mungkin menulis lebih dari byte yang tidak Anda inginkan. Jika string adalah sesuatu seperti struct { int size; const char * data; };(sepertinya Anda anggap) Anda menulis 4 * 4 Bytes = 16 byte pada alamat memori di mana Anda hanya memesan 8 byte pada mesin x86, jadi 8 byte dituliskan di atas data lain (yang dapat merusak tumpukan Anda ). Untungnya, std::stringbiasanya memiliki beberapa optimasi di tempat untuk string pendek, jadi itu harus cukup besar untuk contoh Anda saat menggunakan beberapa implementasi std utama.
hoffmale
@offmale Anda benar sekali, bisa 4 byte, bisa 8, atau bahkan 1 byte. Namun begitu Anda mengetahui ukuran string, Anda juga tahu bahwa memori itu ada di stack dalam lingkup saat ini, dan Anda dapat menggunakannya sesuka Anda. Maksud saya adalah bahwa jika Anda tahu strukturnya, ia dikemas dengan cara yang independen dari informasi apa pun tentang kelas, tidak seperti objek COM yang memiliki uuid yang mengidentifikasi kelas mereka sebagai bagian dari tabel IUnknown's vtable. Pada gilirannya, kompiler mengakses data ini secara langsung melalui inlining, atau fungsi statis yang terkikis.
Dmitry