Почему 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, который является в некоторой степени духовным предком Swift, требуют цепного вызова до доступа self, а не после.

Что является причиной такого выбора, требующего определения полей перед вызовом суперконструктора?

Zomagk
источник
Интересно. Существует ли ограничение, по которому можно размещать вызовы методов (в частности, виртуальные), например, скажем, только после создания цепочки? В C # полям подкласса присваивается значение по умолчанию, затем строится цепочка / суперкласс, а затем вы можете инициализировать их по-настоящему, IIRC ..
Эрик Эйдт
3
Дело в том, что вам нужно инициализировать все поля, прежде чем разрешить неограниченный доступ self.
CodesInChaos
@ErikEidt: в Swift только дополнительные поля автоматически инициализируются равными нулю.
gnasher729

Ответы:

9

В C ++, когда вы создаете производный объект, он начинается как базовый объект во время работы конструктора Base, поэтому во время работы конструктора Base члены Derived даже не существуют. Поэтому их не нужно инициализировать, и их невозможно инициализировать. Только после завершения конструктора Base объект изменяется на производный объект с множеством неинициализированных полей, которые вы затем инициализируете.

В Swift, когда вы создаете производный объект, он является производным объектом с самого начала. Если методы переопределены, то метод Base init уже будет использовать переопределенные методы, которые могут обращаться к переменным производного члена. Следовательно, все производные переменные-члены должны быть инициализированы до вызова метода Base init.

PS. Вы упомянули Objective-C. В Objective-C все автоматически инициализируется равным 0 / nil / NO. Но если это значение не является правильным значением для инициализации переменной, тогда метод Base init может легко вызвать переопределенный метод и использовать еще не инициализированную переменную со значением 0 вместо правильного значения. В Objective-C это не нарушение правил языка (именно так оно определено для работы), а, очевидно, ошибка в вашем коде. В Swift эта ошибка не допускается языком.

PS. Есть комментарий "это производный объект с самого начала или его нельзя наблюдать из-за языковых правил"? Класс Derived инициализировал свои собственные члены до вызова метода Base init, и эти производные члены сохраняют свои значения. Так как он является производным объектом во время инициализации базы называется, или компилятор должен был бы сделать что - то довольно странно. И сразу после того, как метод инициализации Base инициализировал все члены экземпляра Base, он может вызвать переопределенные функции, и это докажет, что он является экземпляром производного класса.

gnasher729
источник
Очень разумно Я взял на себя свободу добавления примера. Не стесняйтесь откатиться, если я был неясен или неправильно понял суть.
Zomagk
Является ли это производным объектом с самого начала, или правила языка делают его ненаблюдаемым? Я думаю, что это последнее.
Дедупликатор
9

Это происходит из правил безопасности Swift, как описано в разделе « Двухфазная инициализация» на странице « Инициализация языкового документа» .

Это гарантирует, что каждое поле установлено перед использованием (особенно указатели, чтобы избежать сбоев).

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

Затем они могут выполнить дальнейшую инициализацию при условии, что объект хорошо сформирован. В частности, все необязательные указатели будут иметь допустимые значения. ноль не подходит для них.

Цель C не сильно отличается, за исключением того, что 0 или nil всегда является допустимым значением, поэтому инициализация первой фазы выполняется распределителем, просто устанавливая все поля в 0. Кроме того, Swift имеет неизменяемые поля, поэтому они должны инициализироваться в первой фазе. , И Swift обеспечивает соблюдение этих правил безопасности.

Jerry101
источник
Конечно, было бы гораздо сложнее с МИ.
Дедупликатор
3

Рассматривать

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

Поэтому не существует простого проекта, обеспечивающего безопасность подрядчика, когда разрешены виртуальные методы , Swift избегает этих проблем, требуя двухэтапной инициализации, что обеспечивает повышенную безопасность для программиста, а также приводит к более сложному языку.

Если вы можете решить эти проблемы хорошим способом, не пропустите «Go», перейдите непосредственно к коллекции вашего доктора философии…

Ян
источник