it-swarm-ja.com

なぜSwiftサブクラスの適切なフィールドを最初に初期化するのですか?

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のような他の多くの言語、さらにはSwiftのいくらか精神的な祖先であるObjective Cでさえ、後ではなくselfにアクセスする前にチェーン呼び出しが必要です。

スーパーコンストラクタを呼び出す前にフィールドを定義する必要があるというこの選択の背後にある理由は何ですか?

9
Zomagk

C++では、Derivedオブジェクトを作成すると、Baseコンストラクターの実行中にオブジェクトがBaseオブジェクトとして開始されるため、Baseコンストラクターの実行時にDerivedメンバーは存在しません。したがって、初期化する必要はなく、初期化することもできません。 Baseコンストラクターが終了した場合にのみ、オブジェクトは、初期化する多くの初期化されていないフィールドを持つDerivedオブジェクトに変更されます。

Swiftでは、Derivedオブジェクトを作成すると、最初からDerivedオブジェクトになります。メソッドがオーバーライドされている場合、Base initメソッドはオーバーライドされたメソッドをすでに使用しており、派生メンバー変数にアクセスする可能性があります。したがって、すべての派生メンバー変数mustは、Base initメソッドが呼び出される前に初期化されます。

PS。あなたはObjective-Cについて言及しました。 Objective-Cでは、すべてが自動的に0/nil/NOに初期化されます。しかし、その値が変数を初期化するための正しい値でない場合、Base initメソッドはオーバーライドされ、まだ初期化されていない変数を正しい値の代わりに値0で使用するメソッドを簡単に呼び出すことができます。 Objective-Cでは、それは言語規則の違反ではありません(つまり、動作するように定義されています)が、コードのバグです。 Swiftでは、そのバグは言語によって許可されていません。

PS。 「最初から派生したオブジェクトなのか、それとも言語のルールで観察できないのか」というコメントがありますか? Derivedクラスは、Base initメソッドが呼び出される前に独自のメンバーを初期化しており、これらのDerivedメンバーは値を保持しています。つまり、Base-initが呼び出されたときのis Derivedオブジェクトか、コンパイラが奇妙なことをする必要があります。そして、Base initメソッドがすべてのBaseインスタンスメンバーを初期化した直後に、オーバーライドされた関数を呼び出すことができ、それが派生クラスのインスタンスであることを証明します。

9
gnasher729

これは、言語ドキュメントの 初期化ページ のセクションTwo-Phase Initializationで説明されているように、Swiftの安全規則に由来します。

これにより、すべてのフィールドが使用前に設定されます(特に、クラッシュを回避するためのポインター)。

Swiftはこれを2フェーズの初期化シーケンスで実現します。各イニシャライザはすべてのインスタンスフィールドを初期化し、スーパークラスのイニシャライザを呼び出して同様に実行する必要があります。その後、ツリーが発生した後にのみ、これらのイニシャライザはselfポインタをエスケープして、呼び出すことができます。インスタンスメソッド、またはインスタンスプロパティの値を読み取ります。

その後、オブジェクトが整形式であることを確認して、さらに初期化を行うことができます。特に、オプションではないすべてのポインターは有効な値を持ちます。 nilはそれらには無効です。

Objective Cは、0またはnilが常に有効な値であることを除いて、それほど違いはありません。したがって、最初のフェーズの初期化は、アロケーターがすべてのフィールドを0に設定することによって行われます。また、Swiftには不変フィールドがありますなので、フェーズ1で初期化する必要があります。Swiftは、これらの安全規則を適用します。

9
Jerry101

検討する

  • 基本クラスで定義された仮想メソッドは、派生クラスで再定義できます。
  • 基本クラスの請負業者は、この仮想メソッドを直接または間接的に呼び出すことができます。
  • redefined仮想メソッド(派生クラス内)は、派生クラスのフィールドの値が派生クラスの請負業者で正しく設定されているかどうかに依存する場合があります。
  • 派生クラスの請負業者は、基本クラスの請負業者に設定されているフィールドに依存する基本クラスのメソッドを呼び出すことができます。

したがって仮想メソッドが許可されている場合に請負業者を安全にする単純な設計はありません、Swiftは、2相初期化を必要とすることでこれらの問題を回避し、プログラマーにより良い安全性を与えます、その一方で、より複雑な言語になります。

これらの問題をうまく解決できる場合は、「Go」を渡さずに、PHdのコレクションに直接進んでください…

3
Ian