Чому Swift ініціалізує спочатку власні підкласи підкласу?


9

Мовою Swift, щоб ініціалізувати екземпляр, потрібно заповнити всі поля цього класу і лише потім викликати суперконструктор:

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)
    }
}

Для мене це здається назад, тому що екземпляр потрібно selfстворити до присвоєння значень своїм полям, і цей код створює враження , ніби ланцюжок відбувається лише після призначення. Крім того, суперклас не має юридичних засобів для читання введених атрибутів підкласу, тому безпека не враховується в цьому випадку.

Крім того, багато інших мов, як JavaScript, і навіть Objective C, який є деяким духовним предком Свіфта, вимагають ланцюгового дзвінка перед доступом self, а не після.

Яке обґрунтування цього вибору вимагає визначити поля перед викликом суперконструктора?


Цікаво. Чи існує обмеження, коли виклики методів (зокрема, віртуальних) можуть бути розміщені, наприклад, лише після ланцюга? У полі C # поля підкласу задано значення за замовчуванням, потім - ланцюжок / побудова надкласу, тоді ви можете ініціалізувати їх для дійсних, IIRC ..
Ерік Ейдт

3
Справа в тому, що вам потрібно ініціалізувати всі поля, перш ніж ви дозволите необмежений доступ до них self.
CodesInChaos

@ErikEidt: У Swift автоматично не ініціюються лише необов'язкові поля.
gnasher729

Відповіді:


9

У C ++, коли ви створюєте отриманий об'єкт, він починається як об'єкт Base, поки працює конструктор Base, тому під час запуску конструктора Base, отримані члени навіть не існують. Тому їх не потрібно ініціалізувати, і вони не можуть бути ініціалізовані. Тільки після закінчення конструктора Base об'єкт змінюється на Отриманий об'єкт з великою кількістю неініціалізованих полів, які ви потім ініціалізуєте.

У Swift, коли ви створюєте Отриманий об'єкт, це об'єкт, що виводиться з самого початку. Якщо методи переосмислені, то метод Base init вже буде використовувати переопределені методи, які можуть отримати доступ до змінних похідних членів. Тому всі змінені похідні члена повинні бути ініціалізовані до виклику методу Bait init.

PS. Ви згадали Objective-C. У Objective-C все автоматично ініціюється до 0 / нуль / НІ. Але якщо це значення не є правильним значенням для ініціалізації змінної, то метод Base init може легко викликати метод, який переосмислений, і використовує ще не ініціалізовану змінну зі значенням 0 замість правильного значення. У Objective-C це не порушення мовних правил (саме так воно визначено для роботи), але, очевидно, помилка у вашому коді. У Swift ця помилка заборонена мовою.

PS. Є коментар "це похідний об'єкт із самого початку, чи він не помітний через мовні правила"? Клас Похідні ініціалізував власних членів до виклику методу Bait init, і ці Похідні члени зберігають свої значення. Так як він є похідним об'єктом під час ініціалізації бази називається, або компілятор мав би зробити що - то досить дивно. І одразу після того, як метод Base init ініціалізує всіх членів екземпляра Base, він може викликати переосмислені функції, і це доведе, що це екземпляр похідного класу.


Дуже розумний. Я взяв свободу додавання прикладу. Не соромтесь відкочуватися, якщо я не зрозумів або неправильно зрозумів суть.
Зомагк

Це похідний об'єкт із самого початку, чи мовні правила роблять це непомітним? Я думаю, що це останнє.
Deduplicator

9

Це відбувається з правил безпеки Свіфта, як описано в розділі двофазним ініціалізації на мовному Дока ініціалізації сторінці .

Це забезпечує, що кожне поле встановлено перед використанням (особливо вказівники, щоб уникнути збоїв).

Swift досягає цього двофазною послідовністю ініціалізації: Кожен ініціалізатор повинен ініціалізувати всі поля свого екземпляра, потім викликати ініціалізатор надкласового класу, щоб зробити так само, і лише після того, як це трапиться вгору дерево, ці ініціалізатори можуть дозволити selfпокажчику вийти, викликати екземпляр методів або зчитувати значення властивостей екземпляра.

Потім вони можуть зробити подальшу ініціалізацію, запевнивши, що об’єкт добре сформований. Зокрема, всі необов'язкові вказівники матимуть дійсні значення. нуль для них не дійсний.

Завдання C не сильно відрізняється, за винятком того, що 0 або нуль - це завжди дійсне значення, тому ініціалізація першої фази здійснюється алокатором, просто встановивши всі поля 0. Також Swift має незмінні поля, тому вони повинні ініціалізуватися в першій фазі . І Свіфт виконує ці правила безпеки.


Звичайно, з ІМ було б набагато важче.
Deduplicator

3

Розглянемо

  • Віртуальний метод, визначений у базовому класі, може бути переосмислений у похідному класі.
  • Підрядник базового класу може викликати цей віртуальний метод прямо або опосередковано.
  • Redefined віртуальний метод (в похідному класі) може залежати від значення поля в похідному класі бути встановлений правильно в похідному класі підрядника.
  • Підрядник похідного класу може викликати метод базового класу, який залежить від полів, встановлених у підряднику базового класу.

Тому не існує простої конструкції, яка б зробила безпеку підрядника, коли дозволені віртуальні методи , Свіфт уникає цих проблем, вимагаючи двофазної ініціалізації, що забезпечує кращу безпеку для програміста, а також призводить до більш складної мови.

Якщо ви зможете вирішити ці питання красивим способом, не пропускайте "Перейти", переходите безпосередньо до колекції вашого PHd ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.