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

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


面白い。チェーンの後にだけなど、メソッド呼び出し(特に仮想)を配置できる制限はありますか?C#では、サブクラスのフィールドは、次に/スーパー建設をチェーン、デフォルト値が与えられ、その後、あなたはIIRC ..本物のためにそれらを初期化することができます
エリックEidt

3
ポイントは、への無制限のアクセスを許可する前に、すべてのフィールドを初期化する必要があるということですself
CodesInChaos

@ErikEidt:Swiftでは、オプションのフィールドのみが自動的にnilに初期化されます。
gnasher729

回答:


9

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

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

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

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


非常に賢明です。私は自由に例を追加しました。ポイントが不明確または誤解された場合は、遠慮なくロールバックしてください。
Zomagk 2016

それは最初からDerivedオブジェクトですか、それとも言語規則によってそれを監視できなくなりますか?後者だと思います。
Deduplicator

9

これは、言語ドキュメントの「初期化」ページのセクション「2フェーズの初期化」で説明されているように、Swiftの安全規則に基づいています

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

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

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

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


もちろん、それはMIでははるかに困難になります。
Deduplicator

3

検討する

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.