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 (seperti a1
), objek buatan yang dibuat ketika inisialisasi berakhir dibuang, atau jika suatu objek diperlukan untuk pengikatan referensi (seperti, dalam A_factory_func();
. Dalam kasus terakhir, sebuah objek dibuat secara artifisial, yang disebut "perwujudan sementara", karena A_factory_func()
tidak memiliki variabel atau referensi yang sebaliknya membutuhkan objek untuk ada).
Sebagai contoh dalam kasus kami, dalam kasus a1
dan a2
aturan khusus mengatakan bahwa dalam deklarasi tersebut, objek hasil penginisialisasi prvalue dari jenis yang sama seperti a1
variabel a1
, dan oleh karena itu A_factory_func()
secara langsung menginisialisasi objek a1
. Setiap pemain gaya fungsional perantara tidak akan memiliki efek apa pun, karena A_factory_func(another-prvalue)
hanya "melewati" objek hasil dari nilai luar menjadi juga objek hasil dari nilai dalam.
A a1 = A_factory_func();
A a2(A_factory_func());
Tergantung pada jenis apa yang A_factory_func()
dikembalikan. Saya menganggap itu mengembalikan A
- kemudian melakukan hal yang sama - kecuali bahwa ketika copy constructor eksplisit, maka yang pertama akan gagal. Baca 8.6 / 14
double b1 = 0.5;
double b2(0.5);
Ini melakukan hal yang sama karena ini adalah tipe bawaan (ini berarti bukan tipe kelas di sini). Baca 8.6 / 14 .
A c1;
A c2 = A();
A c3(A());
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 ke c2
(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 fungsi c3
yang mengembalikan A
dan yang membawa penunjuk fungsi ke fungsi yang mengembalikan a A
(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:
T t(x);
T t = x;
Ada perilaku yang dapat kita atributkan untuk masing-masing:
- Inisialisasi langsung berperilaku seperti pemanggilan fungsi ke fungsi kelebihan beban: Fungsi, dalam hal ini, adalah konstruktor
T
(termasuk explicit
yang), dan argumennya adalah x
. Resolusi kelebihan akan menemukan konstruktor yang paling cocok, dan ketika dibutuhkan akan melakukan konversi implisit yang diperlukan.
- Salin inisialisasi membuat urutan konversi tersirat: Mencoba mengonversi
x
ke objek tipe T
. (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.
#include <iostream>
struct B;
struct A {
operator B();
};
struct B {
B() { }
B(A const&) { std::cout << "<direct> "; }
};
A::operator B() { std::cout << "<copy> "; return B(); }
int main() {
A a;
B b1(a); // 1)
B b2 = a; // 2)
}
// output: <direct> <copy>
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 :
B(A const&)
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 mengetik B
atau berasal darinya (yang jelas terjadi di sini). Jadi akan mencari cara untuk melakukan konversi, dan akan menemukan kandidat berikut
B(A const&)
operator B(A&);
Perhatikan 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 dengan x
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!
A c1; A c2 = c1; A c3(c1);
.