Por que o Swift inicializa os subclasses de campos adequados primeiro?


9

Na linguagem Swift, para inicializar uma instância, é necessário preencher todos os campos dessa classe e somente então chamar superconstrutor:

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)
    }
}

Para mim, parece inverso, porque a instância precisa selfser criada antes da atribuição de valores a seus campos, e esse código dá a impressão de que o encadeamento ocorre apenas após a atribuição. Além disso, a superclasse não possui meios legais de ler os atributos introduzidos de sua subclasse; portanto, a segurança não conta neste caso.

Além disso, muitas outras linguagens, como JavaScript, e até o Objective C, que é um ancestral espiritual do Swift, exigem ligação em cadeia antes do acesso self, e não depois.

Qual é o motivo por trás dessa escolha para exigir que os campos sejam definidos antes de chamar o superconstrutor?


Interessante. Existe uma restrição em que as invocações de métodos (virtuais, em particular) podem ser colocadas, como, digamos, apenas após o encadeamento? Em C #, campos de uma subclasse é dado o valor padrão, então encadeamento construção / superclasse, então você pode inicializar-los de verdade, IIRC ..
Erik Eidt

3
O ponto é que você precisa inicializar todos os campos antes de permitir acesso irrestrito self.
CodesInChaos

@ErikEidt: No Swift, apenas campos opcionais são inicializados automaticamente para zero.
precisa saber é o seguinte

Respostas:


9

No C ++, quando você cria um objeto Derivado, ele inicia como um objeto Base enquanto o construtor Base está em execução; portanto, no momento em que o construtor Base é executado, os membros Derived nem existem. Portanto, eles não precisam ser inicializados e não podem ser inicializados. Somente quando o construtor Base é concluído, o objeto é alterado para um objeto Derivado com muitos campos não inicializados que você inicializa.

No Swift, quando você cria um objeto Derivado, ele é um objeto Derivado desde o início. Se os métodos forem substituídos, o método init da Base já utilizará os métodos substituídos, que podem acessar as variáveis ​​de membro Derivado. Portanto, todas as variáveis ​​de membro Derivado devem ser inicializadas antes da chamada do método Base init.

PS. Você mencionou o Objective-C. No Objective-C, tudo é inicializado automaticamente como 0 / nil / NO. Mas se esse valor não for o valor correto para inicializar uma variável, o método init da Base poderá chamar facilmente um método que será substituído e usar a variável ainda não inicializada com um valor 0 em vez do valor correto. No Objective-C, isso não é uma violação das regras de linguagem (é assim que está definido para funcionar), mas obviamente um bug no seu código. No Swift, esse bug não é permitido pelo idioma.

PS. Há um comentário "é um objeto derivado desde o início ou não é observável devido a regras de linguagem"? A classe Derived inicializou seus próprios membros antes que o método init seja chamado, e esses membros Derived mantêm seus valores. Portanto, é um objeto Derivado no momento em que a base init é chamada, ou o compilador teria que fazer algo bastante bizarro. E logo após o método init da Base ter inicializado todos os membros da instância Base, ele pode chamar funções substituídas e isso provará que é uma instância da classe derivada.


Muito sensível. Tomei a liberdade de adicionar um exemplo. Fique à vontade para reverter se eu não estava claro ou não entendi o ponto.
Zomagk

É um objeto Derivado desde o início, ou as regras de linguagem tornam isso não observável? Eu acho que é o último.
Deduplicator

9

Isso vem das regras de segurança da Swift, conforme explicado na seção Inicialização em duas fases na página Inicialização do documento de idioma .

Ele garante que todos os campos sejam definidos antes do uso (especialmente ponteiros, para evitar falhas).

O Swift consegue isso com uma sequência de inicialização em duas fases: cada inicializador deve inicializar todos os seus campos de instância e, em seguida, chamar um inicializador de superclasse para fazer o mesmo, e somente depois que isso acontece na árvore esses inicializadores podem deixar o selfponteiro escapar, chamar instância métodos ou leia os valores das propriedades da instância.

Eles podem fazer uma inicialização adicional, assegurando que o objeto esteja bem formado. Em particular, todos os ponteiros não opcionais terão valores válidos. nulo não é válido para eles.

O objetivo C não é muito diferente, exceto que 0 ou zero é sempre um valor válido; portanto, a primeira fase de inicialização é feita pelo alocador, definindo todos os campos como 0. Além disso, Swift possui campos imutáveis, portanto, eles devem ser inicializados na fase um. . E a Swift aplica essas regras de segurança.


Claro, seria muito mais difícil com o MI.
Deduplicator

3

Considerar

  • Um método virtual definido na classe base pode ser redefinido na classe derivada.
  • O contratado da classe base pode chamar esse método virtual direta ou indiretamente.
  • O método virtual redefinido (na classe derivada) pode depender do valor de um campo na classe derivada ser definido corretamente no contratante da classe derivada.
  • O contratado da classe derivada pode chamar um método na classe base que depende dos campos que foram definidos no contratante da classe base.

Portanto, não há um design simples que torne o contratante seguro quando métodos virtuais são permitidos ; o Swift evita esses problemas ao exigir a Inicialização em Duas Fases, proporcionando maior segurança ao programador e, ao mesmo tempo, resultando em uma linguagem mais complexa.

Se você pode resolver esses problemas de uma maneira agradável, não passe "Go", prossiga diretamente para a coleção do seu PHd…

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.