Pourquoi Swift initialise-t-il d'abord les champs appropriés de la sous-classe?

9

Dans le langage Swift, pour initialiser une instance, il faut remplir tous les champs de cette classe, et ensuite seulement appeler 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)
    }
}

Pour moi, cela semble à l'envers, car l'instance doit selfêtre créée avant d'affecter des valeurs à ses champs, et ce code donne l'impression que le chaînage ne se produit qu'après l'affectation. En dehors de cela, la superclasse n'a aucun moyen légal de lire les attributs introduits de sa sous-classe, donc la sécurité ne compte pas dans ce cas.

En outre, de nombreux autres langages, comme JavaScript et même Objective C, qui est un ancêtre quelque peu spirituel de Swift, nécessitent un chaînage d'appel avant d'accéder self, pas après.

Quel est le raisonnement derrière ce choix pour exiger que les champs soient définis avant d'appeler le superconstructeur?

Zomagk
la source
Intéressant. Existe-t-il une restriction où les invocations de méthodes (virtuelles, en particulier) peuvent être placées, comme, disons seulement, après le chaînage? En C #, les champs d'une sous-classe reçoivent la valeur par défaut, puis la construction de chaînage / superclasse, puis vous pouvez les initialiser pour de vrai, IIRC ..
Erik Eidt
3
Le fait est que vous devez initialiser tous les champs avant d'autoriser un accès illimité à self.
CodesInChaos
@ErikEidt: Dans Swift, seuls les champs facultatifs sont automatiquement initialisés à nil.
gnasher729

Réponses:

9

En C ++, lorsque vous créez un objet dérivé, il commence en tant qu'objet de base pendant l'exécution du constructeur de base, donc au moment où le constructeur de base s'exécute, les membres dérivés n'existent même pas. Il n'est donc pas nécessaire de les initialiser et il est impossible de les initialiser. Ce n'est que lorsque le constructeur de base est terminé que l'objet est changé en un objet dérivé avec beaucoup de champs non initialisés que vous initialisez ensuite.

Dans Swift, lorsque vous créez un objet dérivé, il s'agit d'un objet dérivé depuis le début. Si les méthodes sont remplacées, la méthode d'initialisation de base utilisera déjà les méthodes substituées, qui pourraient accéder aux variables membres dérivées. Par conséquent, toutes les variables membres dérivées doivent être initialisées avant l'appel de la méthode d'initialisation de base.

PS. Vous avez mentionné Objective-C. Dans Objective-C, tout est automatiquement initialisé à 0 / nil / NO. Mais si cette valeur n'est pas la valeur correcte pour initialiser une variable, la méthode d'initialisation de base pourrait facilement appeler une méthode qui est remplacée et utilise la variable non encore initialisée avec une valeur de 0 au lieu de la valeur correcte. Dans Objective-C, ce n'est pas une violation des règles de langage (c'est ainsi que cela est défini pour fonctionner) mais bien évidemment un bug dans votre code. Dans Swift, ce bogue n'est pas autorisé par la langue.

PS. Il y a un commentaire "est-ce un objet dérivé dès le départ, ou n'est-il pas observable à cause des règles de langage"? La classe Derived a initialisé ses propres membres avant l'appel de la méthode d'initialisation de base, et ces membres dérivés conservent leurs valeurs. Donc , soit il est un objet dérivé au moment de base init est appelé, ou le compilateur aurait à faire quelque chose d' assez bizarre. Et juste après que la méthode init de base a initialisé tous les membres d'instance de base, elle peut appeler des fonctions remplacées et cela prouvera qu'il s'agit d'une instance de la classe dérivée.

gnasher729
la source
Très raisonnable. J'ai pris la liberté d'ajouter un exemple. N'hésitez pas à revenir en arrière si je n'ai pas été clair ou si j'ai mal compris le point.
Zomagk
Est-ce un objet dérivé dès le départ, ou les règles de langage le rendent-elles non observable? Je pense que c'est ce dernier.
Déduplicateur
9

Cela vient des règles de sécurité de Swift, comme expliqué dans la section Initialisation en deux phases sur la page Initialisation du doc ​​de langue .

Il garantit que chaque champ est défini avant utilisation (en particulier les pointeurs, pour éviter les plantages).

Swift y parvient avec une séquence d'initialisation en deux phases: chaque initialiseur doit initialiser tous ses champs d'instance, puis appeler un initialiseur de super-classe pour faire de même, et ce n'est qu'après que cela se produit dans l'arborescence que ces initialiseurs sont autorisés à laisser le selfpointeur s'échapper, appeler l'instance ou lisez les valeurs des propriétés d'instance.

Ils peuvent ensuite effectuer une autre initialisation, assurés que l'objet est bien formé. En particulier, tous les pointeurs non facultatifs auront des valeurs valides. nul n'est pas valable pour eux.

L'objectif C n'est pas très différent, sauf que 0 ou nil est toujours une valeur valide, donc l'initialisation de la première phase est effectuée par l'allocateur en mettant simplement tous les champs à 0. De plus, Swift a des champs immuables, ils doivent donc être initialisés dans la phase un . Et Swift applique ces règles de sécurité.

Jerry101
la source
Bien sûr, ce serait beaucoup plus difficile avec MI.
Déduplicateur
3

Considérer

  • Une méthode virtuelle définie dans la classe de base peut être redéfinie dans la classe dérivée.
  • L'entrepreneur de la classe de base peut appeler cette méthode virtuelle directement ou indirectement.
  • La méthode virtuelle redéfinie (dans la classe dérivée) peut dépendre de la valeur d'un champ de la classe dérivée définie correctement dans l'entrepreneur de classe dérivée.
  • L'entrepreneur de la classe dérivée peut appeler une méthode dans la classe de base qui dépend des champs définis dans l'entrepreneur de la classe de base.

Par conséquent, il n'y a pas de conception simple qui sécurise l'entrepreneur lorsque des méthodes virtuelles sont autorisées , Swift évite ces problèmes en exigeant une initialisation en deux phases, offrant ainsi une meilleure sécurité au programmeur, tout en créant un langage plus complexe.

Si vous pouvez résoudre ces problèmes d'une manière agréable, ne passez pas "Go", passez directement à la collecte de votre PHd ...

Ian
la source