为什么Swift首先要初始化子类的适当字段?


9

在Swift语言中,要初始化一个实例,必须填写该类的所有字段,然后才调用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)
    }
}

在我看来,这是倒退的,因为需要self在将实例赋值给其字段之前创建实例,并且该代码给人的印象是好像链接仅在赋值之后发生。除此之外,超类没有合法的手段来读取其子类的引入属性,因此在这种情况下安全性不算在内。

另外,许多其他语言(例如JavaScript),甚至是Objective C(在Swift上都是精神上的祖先),都需要在访问之前self而不是之后进行链接调用。

在调用超构造函数之前要求定义字段的选择背后的原因是什么?


有趣。是否可以放置方法调用(特别是虚拟方法)(例如,仅在链接之后)是否存在限制?在C#中,一个子类的字段中给出的默认值,然后链接/超结构,那么你就可以初始化它们真实的,IIRC ..
埃里克Eidt

3
关键是要允许无限制的访问,您需要初始化所有字段self
CodesInChaos

@ErikEidt:在Swift中,只有可选字段会自动初始化为nil。
gnasher729

Answers:


9

在C ++中,当您创建Derived对象时,它在Base构造函数运行时以Base对象开始,因此在Base构造函数运行时,Derived成员甚至不存在。因此,它们不需要初始化,也不可能被初始化。仅当Base构造函数完成后,该对象才更改为带有大量未初始化字段的Derived对象,然后进行初始化。

在Swift中,当您创建派生对象时,它从一开始就是一个派生对象。如果方法被覆盖,则Base初始化方法将已经使用覆盖的方法,该方法可以访问派生成员变量。因此,必须在调用Base init方法之前初始化所有派生成员变量。

PS。您提到了Objective-C。在Objective-C中,所有内容都会自动初始化为0 / nil / NO。但是,如果该值不是初始化变量的正确值,则Base init方法可以轻松地调用被覆盖的方法,并使用值为0而不是正确值的尚未初始化的变量。在Objective-C中,这并不违反语言规则(这就是它的定义工作方式),但显然是代码中的错误。在Swift中,该语言不允许该错误。

PS。有一条评论“它是一开始的派生对象,还是由于语言规则而无法观察”?在调用Base init方法之前,Derived类已经初始化了自己的成员,并且这些Derived成员保留其值。因此,要么在调用Base init时它一个派生对象,要么编译器将不得不做一些奇怪的事情。在Base init方法初始化所有Base实例成员之后,就可以调用重写的函数,这将证明它是派生类的实例。


很懂事 我可以自由地添加一个示例。如果我不清楚或误解了重点,请随意回滚。
Zomagk

它是从一开始就衍生的对象,还是语言规则使该对象不可观察?我认为是后者。
Deduplicator

9

这来自Swift的安全规则,如语言文档“ 初始化”页面上的“ 两阶段初始化 ”部分所述。

它确保在使用之前设置每个字段(特别是指针,以避免崩溃)。

Swift通过两阶段的初始化序列实现了这一点:每个初始化程序都必须初始化其所有实例字段,然后调用超类初始化程序进行同样的操作,只有在树上发生这种情况之后,才允许这些初始化程序让self指针转义并调用实例。方法,或读取实例属性的值。

然后,他们可以进行进一步的初始化,以确保对象格式正确。特别是,所有非可选指针将具有有效值。nil对他们无效。

目标C的区别不大,除了0或nil始终是有效值外,因此第一阶段的初始化是由分配器完成的,只需将所有字段设置为0。而且,Swift具有不可变的字段,因此必须在第一阶段进行初始化。 。Swift会执行这些安全规则。


当然,使用MI将更加困难。
Deduplicator

3

考虑

  • 在基类中定义的虚拟方法可以在派生类中重新定义。
  • 基类的承包商可以直接或间接调用此虚拟方法。
  • 重新定义的虚拟方法(在派生类)可以在派生类是一组正确地在派生类承包商取决于字段的值。
  • 派生类的承包商可以调用基类中的方法,该方法取决于已在基类承包商中设置的字段。

因此,没有简单的设计可以在允许使用虚拟方法时使承包商安全。Swift通过要求两阶段初始化来避免这些问题,从而为程序员提供了更好的安全性,同时还导致了更复杂的语言。

如果您可以通过一种很好的方式解决这些问题,请不要通过“开始”操作,而是直接继续收集您的PHd…

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.