Misalkan saya memiliki fungsi ini:
void my_test()
{
A a1 = A_factory_func();
A a2(A_factory_func());
double b1 = 0.5;
double b2(0.5);
A c1;
A c2 = A();
A c3(A());
}
Dalam setiap pengelompokan, apakah pernyataan ini identik? Atau adakah salinan tambahan (mungkin dapat dioptimalkan) di beberapa inisialisasi?
Saya telah melihat orang-orang mengatakan kedua hal itu. Silakan mengutip teks sebagai bukti. Tolong tambahkan juga case lain.
c++
initialization
rbbond
sumber
sumber
A c1; A c2 = c1; A c3(c1);
.A
, inisialisasi salinan tidak memerlukan keberadaan copy / move constuctor. Inilah sebabnya mengapastd::atomic<int> a = 1;
ok di C ++ 17 tetapi tidak sebelumnya.Jawaban:
Pembaruan C ++ 17
Dalam C ++ 17, makna
A_factory_func()
berubah dari menciptakan objek sementara (C ++ <= 14) menjadi hanya menentukan inisialisasi objek apa pun yang menjadi inisialisasi ekspresi ini (dalam bahasa longgar) dalam C ++ 17. Objek-objek ini (disebut "objek hasil") adalah variabel yang dibuat oleh deklarasi (sepertia1
), objek buatan yang dibuat ketika inisialisasi berakhir dibuang, atau jika suatu objek diperlukan untuk pengikatan referensi (seperti, dalamA_factory_func();
. Dalam kasus terakhir, sebuah objek dibuat secara artifisial, yang disebut "perwujudan sementara", karenaA_factory_func()
tidak memiliki variabel atau referensi yang sebaliknya membutuhkan objek untuk ada).Sebagai contoh dalam kasus kami, dalam kasus
a1
dana2
aturan khusus mengatakan bahwa dalam deklarasi tersebut, objek hasil penginisialisasi prvalue dari jenis yang sama sepertia1
variabela1
, dan oleh karena ituA_factory_func()
secara langsung menginisialisasi objeka1
. Setiap pemain gaya fungsional perantara tidak akan memiliki efek apa pun, karenaA_factory_func(another-prvalue)
hanya "melewati" objek hasil dari nilai luar menjadi juga objek hasil dari nilai dalam.Tergantung pada jenis apa yang
A_factory_func()
dikembalikan. Saya menganggap itu mengembalikanA
- kemudian melakukan hal yang sama - kecuali bahwa ketika copy constructor eksplisit, maka yang pertama akan gagal. Baca 8.6 / 14Ini melakukan hal yang sama karena ini adalah tipe bawaan (ini berarti bukan tipe kelas di sini). Baca 8.6 / 14 .
Ini tidak melakukan hal yang sama. Default-inisialisasi pertama
A
adalah non-POD, dan tidak melakukan inisialisasi untuk POD (Baca 8.6 / 9 ). Salinan kedua dimulai: Nilai-menginisialisasi sementara dan kemudian menyalin nilai itu kec2
(Baca 5.2.3 / 2 dan 8.6 / 14 ). Ini tentu saja membutuhkan konstruktor salinan non-eksplisit (Baca 8.6 / 14 dan 12.3.1 / 3 dan 13.3.1.3/1 ). Yang ketiga membuat deklarasi fungsi untuk fungsic3
yang mengembalikanA
dan yang membawa penunjuk fungsi ke fungsi yang mengembalikan aA
(Baca 8.2 ).Menggali Inisialisasi Langsung dan Salin inisialisasi
Walaupun mereka terlihat identik dan seharusnya melakukan hal yang sama, kedua bentuk ini sangat berbeda dalam kasus-kasus tertentu. Dua bentuk inisialisasi adalah langsung dan salin inisialisasi:
Ada perilaku yang dapat kita atributkan untuk masing-masing:
T
(termasukexplicit
yang), dan argumennya adalahx
. Resolusi kelebihan akan menemukan konstruktor yang paling cocok, dan ketika dibutuhkan akan melakukan konversi implisit yang diperlukan.x
ke objek tipeT
. (Ini kemudian dapat menyalin objek tersebut ke objek yang diinisialisasi, sehingga konstruktor salinan juga diperlukan - tetapi ini tidak penting di bawah)Seperti yang Anda lihat, salin inisialisasi dalam beberapa cara merupakan bagian dari inisialisasi langsung sehubungan dengan kemungkinan konversi tersirat: Sementara inisialisasi langsung memiliki semua konstruktor yang tersedia untuk dipanggil, dan selain itu dapat melakukan konversi tersirat yang diperlukan untuk mencocokkan jenis argumen, salin inisialisasi hanya dapat mengatur satu urutan konversi implisit.
Saya berusaha keras dan mendapatkan kode berikut untuk menampilkan teks yang berbeda untuk masing-masing bentuk , tanpa menggunakan "jelas" melalui
explicit
konstruktor.Bagaimana cara kerjanya, dan mengapa itu menghasilkan hasil itu?
Inisialisasi langsung
Pertama tidak tahu apa-apa tentang konversi. Itu hanya akan mencoba memanggil konstruktor. Dalam hal ini, konstruktor berikut tersedia dan merupakan pasangan yang tepat :
Tidak ada konversi, apalagi konversi yang ditentukan pengguna, diperlukan untuk memanggil konstruktor itu (perhatikan bahwa tidak ada konversi kualifikasi konstanta yang terjadi di sini juga). Dan inisialisasi langsung akan menyebutnya.
Salin inisialisasi
Seperti yang dikatakan di atas, salin inisialisasi akan membuat urutan konversi ketika
a
belum mengetikB
atau berasal darinya (yang jelas terjadi di sini). Jadi akan mencari cara untuk melakukan konversi, dan akan menemukan kandidat berikutPerhatikan bagaimana saya menulis ulang fungsi konversi: Jenis parameter mencerminkan jenis
this
penunjuk, yang dalam fungsi non-const adalah untuk non-const. Sekarang, kami menyebut para kandidat ini denganx
argumen. Pemenangnya adalah fungsi konversi: Karena jika kita memiliki dua fungsi kandidat yang sama-sama menerima referensi ke tipe yang sama, maka versi const yang lebih sedikit menang (ini, omong-omong, juga mekanisme yang lebih memilih fungsi anggota non-const meminta objek -const).Perhatikan bahwa jika kita mengubah fungsi konversi menjadi fungsi const member, maka konversi tersebut ambigu (karena keduanya memiliki tipe parameter
A const&
saat itu): Kompilator Comeau menolaknya dengan benar, tetapi GCC menerimanya dalam mode non-pedantic. Beralih ke-pedantic
membuatnya menghasilkan peringatan ambiguitas yang tepat juga.Saya harap ini agak membantu untuk memperjelas perbedaan kedua bentuk ini!
sumber
R() == R(*)()
danT[] == T*
. Yaitu, tipe fungsi adalah tipe pointer fungsi, dan tipe array adalah tipe pointer-to-elemen. Ini menyebalkan. Ini dapat diatasi denganA c3((A()));
(parens sekitar ekspresi).Tugas berbeda dari inisialisasi .
Kedua baris berikut melakukan inisialisasi . Panggilan konstruktor tunggal dilakukan:
tapi itu tidak setara dengan:
Saya tidak memiliki teks saat ini untuk membuktikan ini, tetapi sangat mudah untuk bereksperimen:
sumber
double b1 = 0.5;
adalah panggilan implisit dari konstruktor.double b2(0.5);
adalah panggilan eksplisit.Lihatlah kode berikut untuk melihat perbedaannya:
Jika kelas Anda tidak memiliki konstruktor eksplisit daripada panggilan eksplisit dan implisit adalah identik.
sumber
Pengelompokan pertama: itu tergantung pada apa yang
A_factory_func
kembali. Baris pertama adalah contoh inisialisasi salin , baris kedua adalah inisialisasi langsung . JikaA_factory_func
mengembalikanA
objek maka mereka setara, mereka berdua memanggil konstruktor salin untukA
, jika tidak versi pertama membuat nilai tipeA
dari operator konversi yang tersedia untuk tipe kembaliA_factory_func
atauA
konstruktor yang sesuai , dan kemudian memanggil konstruktor salin untuk membanguna1
dari ini sementara. Versi kedua mencoba untuk menemukan konstruktor yang cocok yang mengambilA_factory_func
pengembalian apa pun , atau yang mengambil sesuatu yang nilai pengembaliannya dapat secara implisit dikonversi.Pengelompokan kedua: logika yang persis sama berlaku, kecuali bahwa tipe yang dibangun tidak memiliki konstruktor yang eksotis sehingga dalam praktiknya identik.
Pengelompokan ketiga:
c1
default diinisialisasi,c2
disalin-diinisialisasi dari nilai yang diinisialisasi sementara. Setiap anggotac1
yang memiliki tipe pod (atau anggota anggota, dll., Dll.) Mungkin tidak diinisialisasi jika pengguna memberikan konstruktor default (jika ada) tidak menginisialisasi mereka secara eksplisit. Sebabc2
, itu tergantung pada apakah ada pengguna yang menyediakan salinan konstruktor dan apakah yang secara tepat menginisialisasi anggota tersebut, tetapi anggota sementara semua akan diinisialisasi (nol diinisialisasi jika tidak diinisialisasi secara eksplisit). Seperti litb yang terlihat,c3
adalah jebakan. Ini sebenarnya adalah deklarasi fungsi.sumber
Catatan:
[12.2 / 1]
Temporaries of class type are created in various contexts: ... and in some initializations (8.5).
Yaitu, untuk inisialisasi salin.
[12.8 / 15]
When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...
Dengan kata lain, kompiler yang baik tidak akan membuat salinan untuk inisialisasi salin jika dapat dihindari; sebagai gantinya hanya akan memanggil konstruktor secara langsung - yaitu, seperti untuk inisialisasi langsung.
Dengan kata lain, inisialisasi salin sama seperti inisialisasi langsung dalam kebanyakan kasus <opinion> di mana kode yang dapat dimengerti telah ditulis. Karena inisialisasi langsung berpotensi menyebabkan konversi sewenang-wenang (dan karena itu mungkin tidak diketahui), saya lebih suka untuk selalu menggunakan inisialisasi salin jika memungkinkan. (Dengan bonus itu sebenarnya seperti inisialisasi.) </opinion>
Kegagalan teknis: [12.2 / 1 lanj dari dari atas]
Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.
Senang saya tidak menulis kompiler C ++.
sumber
Anda bisa melihat perbedaannya dalam tipe
explicit
danimplicit
konstruktor ketika Anda menginisialisasi objek:Kelas:
Dan dalam
main
fungsinya:Secara default, konstruktor adalah
implicit
demikian Anda memiliki dua cara untuk menginisialisasi:Dan dengan mendefinisikan struktur
explicit
hanya Anda memiliki satu cara langsung:sumber
Menjawab sehubungan dengan bagian ini:
Karena sebagian besar jawabannya adalah pra-c ++ 11 saya menambahkan apa yang dikatakan c ++ 11 tentang ini:
Jadi optimasi atau tidak mereka setara sesuai standar. Perhatikan bahwa ini sesuai dengan apa yang disebutkan oleh jawaban lain. Mengutip apa yang dikatakan standar demi kebenaran.
sumber
Banyak kasus-kasus ini tunduk pada implementasi objek sehingga sulit untuk memberikan jawaban yang konkret.
Pertimbangkan kopernya
Dalam hal ini dengan asumsi operator penugasan yang tepat & inisialisasi konstruktor yang menerima argumen integer tunggal, bagaimana saya menerapkan metode tersebut mempengaruhi perilaku setiap baris. Namun itu adalah praktik umum bagi salah satu dari mereka untuk memanggil yang lain dalam implementasi untuk menghilangkan kode duplikat (meskipun dalam kasus sesederhana ini tidak akan ada tujuan nyata.)
Sunting: Seperti disebutkan dalam respons lain, baris pertama sebenarnya akan memanggil pembuat salinan. Pertimbangkan komentar yang berkaitan dengan operator penugasan sebagai perilaku yang berkaitan dengan penugasan yang berdiri sendiri.
Yang mengatakan, bagaimana kompiler mengoptimalkan kode maka akan memiliki dampaknya sendiri. Jika saya memiliki konstruktor inisialisasi yang memanggil operator "=" - jika kompiler tidak membuat optimasi, baris paling atas kemudian akan melakukan 2 lompatan sebagai lawan satu di baris bawah.
Sekarang, untuk situasi yang paling umum, kompiler Anda akan mengoptimalkan melalui kasus-kasus ini dan menghilangkan jenis inefisiensi ini. Jadi secara efektif semua situasi berbeda yang Anda gambarkan akan menjadi sama. Jika Anda ingin melihat dengan tepat apa yang sedang dilakukan, Anda dapat melihat kode objek atau output perakitan dari kompiler Anda.
sumber
operator =(const int)
dan tidakA(const int)
. Lihat jawaban @ jia3ep untuk lebih jelasnya.Ini dari Bahasa Pemrograman C ++ oleh Bjarne Stroustrup:
Inisialisasi dengan = dianggap sebagai inisialisasi salinan . Pada prinsipnya, salinan penginisialisasi (objek tempat kita menyalin) ditempatkan ke objek yang diinisialisasi. Namun, salinan tersebut dapat dioptimalkan jauh (elided), dan operasi perpindahan (berdasarkan semantik langkah) dapat digunakan jika penginisialisasi adalah nilai. Meninggalkan = membuat inisialisasi eksplisit. Inisialisasi eksplisit dikenal sebagai inisialisasi langsung .
sumber