Misalkan saya memiliki kelas dengan memebers pribadi ptr
, name
, pname
, rname
, crname
dan age
. Apa yang terjadi jika saya tidak menginisialisasi sendiri? Berikut ini sebuah contoh:
class Example {
private:
int *ptr;
string name;
string *pname;
string &rname;
const string &crname;
int age;
public:
Example() {}
};
Dan kemudian saya lakukan:
int main() {
Example ex;
}
Bagaimana anggota diinisialisasi dalam mantan? Apa yang terjadi dengan pointer? Apakah string
dan int
mendapatkan 0-intialized dengan konstruktor default string()
dan int()
? Bagaimana dengan anggota referensi? Juga bagaimana dengan referensi const?
Apa lagi yang harus saya ketahui?
Adakah yang tahu tutorial yang membahas kasus-kasus ini? Mungkin di beberapa buku? Saya memiliki akses di perpustakaan universitas ke banyak buku C ++.
Saya ingin mempelajarinya sehingga saya dapat menulis program yang lebih baik (bebas bug). Umpan balik apa pun akan membantu!
sumber
Jawaban:
Sebagai pengganti inisialisasi eksplisit, inisialisasi anggota di kelas bekerja identik dengan inisialisasi variabel lokal dalam fungsi.
Untuk objek , konstruktor defaultnya disebut. Misalnya, untuk
std::string
, konstruktor default menetapkannya ke string kosong. Jika kelas objek tidak memiliki konstruktor default, itu akan menjadi kesalahan kompilasi jika Anda tidak menginisialisasi secara eksplisit.Untuk tipe primitif (pointer, ints, dll), mereka tidak diinisialisasi - mereka berisi sampah apa pun yang kebetulan berada di lokasi memori sebelumnya.
Untuk referensi (misalnya
std::string&
), adalah ilegal untuk tidak menginisialisasi mereka, dan kompiler Anda akan mengeluh dan menolak untuk mengkompilasi kode tersebut. Referensi harus selalu diinisialisasi.Jadi, dalam kasus spesifik Anda, jika tidak diinisialisasi secara eksplisit:
sumber
foo
memang memiliki konstruktor, itu hanya implisit. Tapi itu benar-benar argumen semantik.Pertama, izinkan saya menjelaskan apa itu mem-initializer-list . Sebuah daftar mem-initializer- adalah daftar dipisahkan koma mem-initializer s, di mana masing-masing mem-initializer adalah nama anggota diikuti oleh
(
, diikuti oleh ekspresi-daftar , diikuti oleh)
. Daftar ekspresi adalah bagaimana anggota dibangun. Misalnya, dalamyang mem-initializer-list dari yang disediakan pengguna, tidak ada argumen konstruktor adalah
name(s_str, s_str + 8), rname(name), crname(name), age(-4)
. Ini mem-initializer-list berarti bahwaname
anggota diinisialisasi oleh parastd::string
konstruktor yang mengambil dua iterator masukan , yangrname
anggota diinisialisasi dengan mengacuname
, padacrname
anggota diinisialisasi dengan const-referensiname
, danage
anggota diinisialisasi dengan nilai-4
.Setiap konstruktor memiliki mem-inisialisasi-daftar sendiri , dan anggota hanya dapat diinisialisasi dalam urutan yang ditentukan (pada dasarnya urutan di mana anggota dinyatakan dalam kelas). Dengan demikian, para anggota
Example
dapat hanya diinisialisasi dalam urutan:ptr
,name
,pname
,rname
,crname
, danage
.Ketika Anda tidak menentukan mem-initializer anggota, standar C ++ mengatakan:
Di sini, karena
name
merupakan anggota data non-statis dari tipe kelas, ini diinisialisasi-standar jika tidak ada initializer untukname
ditentukan dalam daftar mem-initializer-list . Semua anggota lainExample
tidak memiliki tipe kelas, jadi mereka tidak diinisialisasi.Ketika standar mengatakan bahwa mereka tidak diinisialisasi, ini berarti bahwa mereka dapat memiliki nilai apa pun . Jadi, karena kode di atas tidak menginisialisasi
pname
, bisa jadi apa saja.Perhatikan bahwa Anda masih harus mengikuti aturan lain, seperti aturan bahwa referensi harus selalu diinisialisasi. Ini adalah kesalahan kompiler untuk tidak menginisialisasi referensi.
sumber
.h
) dan definisi (dalam.cpp
) secara ketat tanpa menunjukkan terlalu banyak internal.Anda juga dapat menginisialisasi anggota data pada saat Anda menyatakannya:
Saya menggunakan formulir ini cukup banyak secara eksklusif, meskipun saya telah membaca beberapa orang menganggapnya sebagai 'bentuk buruk', mungkin karena baru saja diperkenalkan - saya pikir dalam C ++ 11. Bagi saya itu lebih logis.
Aspek lain yang berguna untuk aturan baru adalah bagaimana menginisialisasi data-anggota yang merupakan kelas mereka sendiri. Sebagai contoh anggaplah itu
CDynamicString
adalah kelas yang merangkum penanganan string. Ini memiliki konstruktor yang memungkinkan Anda menentukan nilai awalCDynamicString(wchat_t* pstrInitialString)
. Anda mungkin menggunakan kelas ini sebagai anggota data di dalam kelas lain - katakanlah kelas yang merangkum nilai registri windows yang dalam hal ini menyimpan alamat pos. Untuk 'kode keras' nama kunci registri yang ditulis ini Anda gunakan kawat gigi:Perhatikan kelas string kedua yang memegang alamat pos sebenarnya tidak memiliki initializer sehingga konstruktor defaultnya akan dipanggil pada kreasi - mungkin secara otomatis mengaturnya ke string kosong.
sumber
Jika Anda contoh kelas instantiated pada stack, konten anggota skalar yang tidak diinisialisasi adalah acak dan tidak terdefinisi.
Untuk turunan global, anggota skalar yang tidak diinisialisasi akan di-zeroed.
Untuk anggota yang merupakan instance kelas, konstruktor default mereka akan dipanggil, sehingga objek string Anda akan diinisialisasi.
int *ptr;
// pointer tidak diinisialisasi (atau memusatkan perhatian jika global)string name;
// constructor dipanggil, diinisialisasi dengan string kosongstring *pname;
// pointer tidak diinisialisasi (atau memusatkan perhatian jika global)string &rname;
// kesalahan kompilasi jika Anda gagal menginisialisasi iniconst string &crname;
// kesalahan kompilasi jika Anda gagal menginisialisasi iniint age;
// nilai skalar, tidak diinisialisasi dan acak (atau nol jika global)sumber
string name
kosong setelah menginisialisasi kelas pada stack. Apakah Anda benar-benar yakin dengan jawaban Anda?Anggota non-statis yang diinisialisasi akan berisi data acak. Sebenarnya, mereka hanya akan memiliki nilai lokasi memori yang ditugaskan untuk mereka.
Tentu saja untuk parameter objek (seperti
string
) konstruktor objek dapat melakukan inisialisasi default.Dalam contoh Anda:
sumber
Anggota dengan konstruktor akan meminta konstruktor default mereka dipanggil untuk inisialisasi.
Anda tidak dapat bergantung pada konten dari jenis lainnya.
sumber
Jika ada di tumpukan, konten anggota yang tidak diinisialisasi yang tidak memiliki konstruktor sendiri akan acak dan tidak terdefinisi. Bahkan jika itu bersifat global, itu akan menjadi ide yang buruk untuk mengandalkan mereka menjadi nol. Apakah itu di stack atau tidak, jika anggota memiliki konstruktor sendiri, yang akan dipanggil untuk menginisialisasi itu.
Jadi, jika Anda memiliki string * pname, pointer akan berisi sampah acak. tetapi untuk nama string, konstruktor default untuk string akan dipanggil, memberi Anda string kosong. Untuk variabel jenis referensi Anda, saya tidak yakin, tetapi mungkin itu akan menjadi referensi untuk sejumlah memori acak.
sumber
Itu tergantung pada bagaimana kelas dibangun
Menjawab pertanyaan ini datang dengan memahami pergantian kasus besar dalam standar bahasa C ++, dan yang sulit bagi manusia biasa untuk mendapatkan intuisi.
Sebagai contoh sederhana betapa sulitnya:
main.cpp
Dalam inisialisasi default Anda akan mulai dari: https://en.cppreference.com/w/cpp/language/default_initialisation kita pergi ke bagian "Efek inisialisasi default adalah" dan memulai pernyataan kasus:
Kemudian, jika seseorang memutuskan untuk menginisialisasi nilai, kita buka https://en.cppreference.com/w/cpp/language/value_initialization "Efek dari inisialisasi nilai adalah" dan memulai pernyataan kasus:
= delete
)Inilah sebabnya saya sangat menyarankan agar Anda tidak pernah bergantung pada inisialisasi nol "implisit". Kecuali ada alasan kinerja yang kuat, inisialisasi semuanya secara eksplisit, baik pada konstruktor jika Anda mendefinisikannya, atau menggunakan inisialisasi agregat. Kalau tidak, Anda membuat hal-hal yang sangat berisiko bagi pengembang masa depan.
sumber