Mengapa Swift menginisialisasi subclass bidang yang tepat pertama?

9

Dalam bahasa Swift, untuk menginisialisasi instance, kita harus mengisi semua bidang kelas itu, dan baru kemudian memanggil superconstructor:

class Base {
    var name: String

    init(name: String) {
        self.name = name
    }
}

class Derived: Base {
    var number: Int

    init(name: String, number: Int) {
        // won't compile if interchange lines
        self.number = number
        super.init(name)
    }
}

Bagi saya sepertinya mundur, karena instance selfharus dibuat sebelum memberikan nilai ke bidangnya, dan kode itu memberi kesan seolah-olah rantai hanya terjadi setelah penugasan. Selain itu, superclass tidak memiliki sarana hukum untuk membaca atribut yang diperkenalkan subkelasnya, sehingga keselamatan tidak diperhitungkan dalam kasus ini.

Juga, banyak bahasa lain, seperti JavaScript, dan bahkan Objective C, yang merupakan nenek moyang spiritual Swift, membutuhkan panggilan berantai sebelum mengakses self, bukan setelahnya.

Apa alasan di balik pilihan ini untuk mengharuskan bidang harus didefinisikan sebelum memanggil superconstructor?

Zomagk
sumber
Menarik. Apakah ada batasan di mana metode invokasi (virtual, khususnya) dapat ditempatkan, seperti, katakan saja, setelah rantai? Dalam C #, bidang subclass diberi nilai default, lalu konstruksi rantai / superclass, lalu Anda dapat menginisialisasi mereka secara nyata, IIRC ..
Erik Eidt
3
Intinya adalah bahwa Anda perlu menginisialisasi semua bidang sebelum Anda mengizinkan akses tanpa batas self.
CodesInChaos
@ErikEidt: Di Swift, hanya bidang opsional yang diinisialisasi secara otomatis hingga nihil.
gnasher729

Jawaban:

9

Di C ++, ketika Anda membuat objek yang diturunkan, itu dimulai sebagai objek basis saat konstruktor basis berjalan, jadi pada saat konstruktor basis berjalan, anggota yang diturunkan bahkan tidak ada. Jadi mereka tidak perlu diinisialisasi, dan mereka tidak mungkin diinisialisasi. Hanya ketika konstruktor Basis selesai, objek diubah menjadi objek Turunan dengan banyak bidang yang tidak diinisialisasi yang kemudian Anda inisialisasi.

Di Swift, saat Anda membuat objek yang diturunkan, itu adalah objek yang diturunkan dari awal. Jika metode diganti, maka metode Base init sudah akan menggunakan metode yang diganti, yang mungkin mengakses variabel anggota turunan. Oleh karena itu semua variabel anggota yang diturunkan harus diinisialisasi sebelum metode Base init dipanggil.

PS. Anda menyebutkan Objective-C. Di Objective-C, semuanya secara otomatis diinisialisasi ke 0 / nil / NO. Tetapi jika nilai itu bukan nilai yang benar untuk menginisialisasi variabel, maka metode Base init dapat dengan mudah memanggil metode yang diganti dan menggunakan variabel yang belum diinisialisasi dengan nilai 0 bukannya nilai yang benar. Di Objective-C, itu bukan pelanggaran aturan bahasa (begitulah didefinisikan untuk bekerja) tetapi jelas bug dalam kode Anda. Di Swift, bug itu tidak diizinkan oleh bahasa.

PS. Ada komentar "apakah itu objek yang diturunkan dari awal, atau tidak dapat diamati karena aturan bahasa"? Kelas Derived telah menginisialisasi anggotanya sendiri sebelum metode Base init dipanggil, dan anggota yang diturunkan ini mempertahankan nilainya. Jadi itu adalah objek Derived pada saat Base init dipanggil, atau kompiler harus melakukan sesuatu yang agak aneh. Dan tepat setelah metode Base init menginisialisasi semua anggota instance Base, ia dapat memanggil fungsi yang ditimpa dan yang akan membuktikannya adalah turunan dari kelas turunan.

gnasher729
sumber
Sangat masuk akal. Saya mengambil kebebasan untuk menambahkan contoh. Jangan ragu untuk mundur jika saya tidak jelas atau salah paham intinya.
Zomagk
Apakah ini objek yang diturunkan sejak awal, atau apakah aturan bahasa membuatnya tidak dapat diamati? Saya pikir itu yang terakhir.
Deduplicator
9

Ini berasal dari aturan keselamatan Swift, seperti yang dijelaskan dalam bagian Inisialisasi Dua Fase pada halaman Inisialisasi dokumen bahasa .

Ini memastikan bahwa setiap bidang diatur sebelum digunakan (esp. Pointer, untuk menghindari crash).

Swift mencapai ini dengan urutan inisialisasi dua fase: Setiap penginisialisasi harus menginisialisasi semua bidang instance, kemudian memanggil penginisialisasi superclass untuk melakukan hal yang sama, dan hanya setelah itu terjadi pohon adalah inisialisasi ini diizinkan untuk membiarkan selfpointer keluar, sebut instance metode, atau baca nilai properti instance.

Mereka kemudian dapat melakukan inisialisasi lebih lanjut, meyakinkan bahwa objek tersebut terbentuk dengan baik. Secara khusus, semua pointer non-opsional akan memiliki nilai yang valid. nihil tidak berlaku untuk mereka.

Sasaran C tidak jauh berbeda kecuali bahwa 0 atau nil selalu merupakan nilai yang valid, jadi inisialisasi fase pertama dilakukan oleh pengalokasi hanya mengatur semua bidang ke 0. Juga, Swift memiliki bidang yang tidak dapat diubah, sehingga mereka harus diinisialisasi dalam fase satu . Dan Swift memberlakukan aturan keselamatan ini.

Jerry101
sumber
Tentu saja, akan jauh lebih sulit dengan MI.
Deduplicator
3

Mempertimbangkan

  • Metode virtual yang didefinisikan dalam kelas dasar dapat didefinisikan ulang di kelas turunan.
  • Kontraktor kelas dasar dapat memanggil metode virtual ini secara langsung atau tidak langsung.
  • The didefinisikan ulang metode virtual (di kelas turunan) tergantung pada nilai sebuah lapangan di kelas menjadi set yang berasal dengan benar di kontraktor kelas turunan.
  • Kontraktor kelas turunan dapat memanggil metode dalam kelas dasar yang tergantung pada bidang yang telah ditetapkan dalam kontraktor kelas dasar.

Oleh karena itu tidak ada desain sederhana yang membuat kontraktor aman ketika metode virtual diizinkan , Swift menghindari masalah ini dengan memerlukan Inisialisasi Dua Fase, sehingga memberikan keamanan yang lebih baik kepada programmer, sementara juga menghasilkan bahasa yang lebih kompleks.

Jika Anda dapat memecahkan masalah ini dengan cara yang baik, jangan lulus "Pergi", lanjutkan langsung ke pengumpulan PHd Anda ...

Ian
sumber