Bagaimana anggota kelas C ++ diinisialisasi jika saya tidak melakukannya secara eksplisit?


158

Misalkan saya memiliki kelas dengan memebers pribadi ptr, name, pname, rname, crnamedan 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 stringdan intmendapatkan 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!


3
Untuk rekomendasi buku, lihat stackoverflow.com/questions/388242/…
Mike Seymour

Mike, ow, maksudku bab dari beberapa buku yang menjelaskannya. Tidak seluruh buku! :)
bodacydo

Mungkin sebaiknya Anda membaca seluruh buku tentang bahasa yang ingin Anda programkan. Dan jika Anda sudah membaca satu dan itu tidak menjelaskan ini, maka itu bukan buku yang sangat bagus.
Tyler McHenry

2
Scott Meyers (guru mantan guru C ++ pro-pro populer) menyatakan dalam C ++ Efektif , "aturannya rumit - terlalu rumit untuk dihafalkan, menurut pendapat saya .... pastikan bahwa semua konstruktor menginisialisasi segala sesuatu di objek." Jadi menurut pendapatnya, cara termudah untuk (berusaha) menulis kode "bebas bug" adalah tidak mencoba untuk menghafal aturan (dan sebenarnya dia tidak meletakkannya di buku), tetapi untuk secara eksplisit menginisialisasi semuanya. Namun, perlu diketahui bahwa bahkan jika Anda mengambil pendekatan ini dalam kode Anda sendiri, Anda dapat mengerjakan proyek yang ditulis oleh orang-orang yang tidak, sehingga aturan mungkin masih berharga.
Kyle Strand

2
@TylerMcHenry Buku apa di C ++ yang Anda anggap "bagus"? Saya telah membaca beberapa buku tentang C ++, tetapi tidak satupun dari mereka yang menjelaskan ini sepenuhnya. Seperti disebutkan dalam komentar saya sebelumnya, Scott Meyers secara eksplisit menolak untuk memberikan aturan lengkap dalam C ++ Efektif . Saya juga membaca Meyers ' Effective Modern C ++ , Dewhurst's C ++ Common Knowledge , dan Stroustrup's A Tour of C ++ . Seingat saya, tidak ada yang menjelaskan aturan lengkap. Jelas saya bisa membaca standar, tetapi saya tidak akan menganggap itu "buku bagus"! : D Dan saya berharap Stroustrup mungkin menjelaskannya dalam Bahasa Pemrograman C ++ .
Kyle Strand

Jawaban:


207

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:

    int *ptr;  // Contains junk
    string name;  // Empty string
    string *pname;  // Contains junk
    string &rname;  // Compile error
    const string &crname;  // Compile error
    int age;  // Contains junk

4
+1. Perlu dicatat bahwa, dengan definisi standar yang ketat, contoh tipe primitif bersama dengan berbagai hal lainnya (setiap wilayah penyimpanan) semuanya dianggap objek .
stinky472

7
"Jika kelas objek tidak memiliki konstruktor default, itu akan menjadi kesalahan kompilasi jika Anda tidak menginisialisasi secara eksplisit" itu salah ! Jika kelas tidak memiliki konstruktor default, itu diberikan konstruktor default default yang kosong.
Wizard

19
@wiz Saya pikir dia benar-benar berarti 'jika objek tidak memiliki konstruktor default' karena tidak ada yang dihasilkan juga, yang akan menjadi kasus jika kelas secara eksplisit mendefinisikan konstruktor selain dari default (tidak ada default ctor akan dihasilkan). Jika kita menjadi terlalu pedatis, kita mungkin akan membingungkan lebih dari sekadar bantuan dan Tyler membuat poin yang bagus tentang hal ini dalam menanggapi saya sebelumnya.
stinky472

8
@ wiz-loz Saya akan mengatakan bahwa foomemang memiliki konstruktor, itu hanya implisit. Tapi itu benar-benar argumen semantik.
Tyler McHenry

4
Saya menafsirkan "konstruktor default" sebagai konstruktor yang dapat dipanggil tanpa argumen. Ini akan menjadi salah satu yang Anda tetapkan sendiri atau secara implisit dihasilkan oleh kompiler. Jadi kekurangannya, berarti tidak didefinisikan sendiri atau dihasilkan. Atau begitulah cara saya melihatnya.
5ound

28

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, dalam

static const char s_str[] = "bodacydo";
class Example
{
private:
    int *ptr;
    string name;
    string *pname;
    string &rname;
    const string &crname;
    int age;

public:
    Example()
        : name(s_str, s_str + 8), rname(name), crname(name), age(-4)
    {
    }
};

yang 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 bahwa nameanggota diinisialisasi oleh para std::stringkonstruktor yang mengambil dua iterator masukan , yang rnameanggota diinisialisasi dengan mengacu name, pada crnameanggota diinisialisasi dengan const-referensi name, dan ageanggota 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 Exampledapat hanya diinisialisasi dalam urutan: ptr, name, pname, rname, crname, dan age.

Ketika Anda tidak menentukan mem-initializer anggota, standar C ++ mengatakan:

Jika entitas adalah anggota data tidak statis ... dari tipe kelas ..., entitas tersebut diinisialisasi-awal (8.5). ... Kalau tidak, entitas tidak diinisialisasi.

Di sini, karena namemerupakan anggota data non-statis dari tipe kelas, ini diinisialisasi-standar jika tidak ada initializer untuk nameditentukan dalam daftar mem-initializer-list . Semua anggota lain Exampletidak 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.


Ini adalah cara terbaik untuk menginisialisasi anggota ketika Anda ingin memisahkan deklarasi (dalam .h) dan definisi (dalam .cpp) secara ketat tanpa menunjukkan terlalu banyak internal.
Matthieu

12

Anda juga dapat menginisialisasi anggota data pada saat Anda menyatakannya:

class another_example{
public:
    another_example();
    ~another_example();
private:
    int m_iInteger=10;
    double m_dDouble=10.765;
};

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 CDynamicStringadalah kelas yang merangkum penanganan string. Ini memiliki konstruktor yang memungkinkan Anda menentukan nilai awal CDynamicString(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:

class Registry_Entry{
public:
    Registry_Entry();
    ~Registry_Entry();
    Commit();//Writes data to registry.
    Retrieve();//Reads data from registry;
private:
    CDynamicString m_cKeyName{L"Postal Address"};
    CDynamicString m_cAddress;
};

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.


9

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 kosong
  • string *pname; // pointer tidak diinisialisasi (atau memusatkan perhatian jika global)
  • string &rname; // kesalahan kompilasi jika Anda gagal menginisialisasi ini
  • const string &crname; // kesalahan kompilasi jika Anda gagal menginisialisasi ini
  • int age; // nilai skalar, tidak diinisialisasi dan acak (atau nol jika global)

Saya bereksperimen dan tampaknya itu string namekosong setelah menginisialisasi kelas pada stack. Apakah Anda benar-benar yakin dengan jawaban Anda?
bodacydo

1
string akan memiliki konstruktor yang menyediakan string kosong secara default - saya akan mengklarifikasi jawaban saya
Paul Dixon

@bodacydo: Paul benar, tetapi jika Anda peduli dengan perilaku ini, tidak ada salahnya untuk eksplisit. Lemparkan ke dalam daftar penginisialisasi.
Stephen

Terima kasih telah menjelaskan dan menjelaskan!
bodacydo

2
Itu tidak acak! Acak adalah kata yang terlalu besar untuk itu! Jika anggota skalar akan acak, kita tidak akan memerlukan generator nomor acak lainnya. Bayangkan sebuah program yang menganalisis data "left-overs" - seperti file yang tidak terhapus dalam memori - datanya jauh dari acak. Itu bahkan tidak terdefinisi! Biasanya sulit ditentukan, karena biasanya kita tidak tahu apa yang dilakukan mesin kami. Jika "data acak" yang baru saja Anda hapus adalah satu-satunya gambar ayah Anda, ibumu bahkan mungkin menganggapnya ofensif jika Anda mengatakan bahwa itu acak ...
slyy2048

5

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:

int *ptr; // will point to a random memory location
string name; // empty string (due to string's default costructor)
string *pname; // will point to a random memory location
string &rname; // it would't compile
const string &crname; // it would't compile
int age; // random value

2

Anggota dengan konstruktor akan meminta konstruktor default mereka dipanggil untuk inisialisasi.

Anda tidak dapat bergantung pada konten dari jenis lainnya.


0

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.


0

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

#include <cassert>

int main() {
    struct C { int i; };

    // This syntax is called "default initialization"
    C a;
    // i undefined

    // This syntax is called "value initialization"
    C b{};
    assert(b.i == 0);
}

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:

  • "jika T adalah non-POD ": tidak (definisi POD itu sendiri merupakan pernyataan peralihan besar)
  • "jika T adalah tipe array": no
  • "jika tidak, tidak ada yang dilakukan": karena itu dibiarkan dengan nilai yang tidak ditentukan

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:

  • "jika T adalah tipe kelas tanpa konstruktor default atau dengan konstruktor default yang disediakan atau dihapus pengguna": tidak demikian. Anda sekarang akan menghabiskan 20 menit untuk Googling istilah-istilah itu:
    • kami memiliki konstruktor default yang didefinisikan secara implisit (khususnya karena tidak ada konstruktor lain yang didefinisikan)
    • itu tidak disediakan oleh pengguna (didefinisikan secara implisit)
    • itu tidak dihapus ( = delete)
  • "jika T adalah tipe kelas dengan konstruktor default yang tidak disediakan pengguna atau dihapus": ya
    • "objek diinisialisasi-nol dan kemudian diinisialisasi-awal jika memiliki konstruktor default non-sepele": tidak ada konstruktor non-sepele, hanya inisialisasi nol. Definisi "nol-inisialisasi" setidaknya sederhana dan melakukan apa yang Anda harapkan: https://en.cppreference.com/w/cpp/language/zero_initialization

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.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.