O que é vinculação antecipada e tardia?


77

Continuo ouvindo falar sobre encadernação antecipada e tardia, mas não entendo o que são. Encontrei a seguinte explicação que não entendo:

A ligação antecipada refere-se à atribuição de valores a variáveis ​​durante o tempo de design, enquanto a ligação tardia refere-se à atribuição de valores a variáveis ​​durante o tempo de execução.

Alguém poderia definir os dois tipos de ligação e compará-los?


1
tempo de compilação versus tempo de execução.
barlop

Respostas:


84

Existem dois conceitos principais em confusão: encadernação e carregamento. É conflitado pelo conceito de DataBinding, que está em algum lugar no meio frequentemente fazendo as duas coisas. Depois de considerar, vou acrescentar mais um conceito, para completar a trifecta, despacho.

Tipos

Associação tardia : o tipo é desconhecido até que a variável seja exercida durante o tempo de execução; geralmente através de atribuição, mas existem outros meios para coagir um tipo; linguagens de tipo dinâmico chamam isso de recurso subjacente, mas muitas linguagens de tipo estaticamente têm algum método para obter ligação tardia

Implementado frequentemente usando tipos dinâmicos [especiais], introspecção / reflexão, sinalizadores e opções do compilador, ou através de métodos virtuais, emprestando e estendendo o envio dinâmico

Ligação antecipada : o tipo é conhecido antes que a variável seja exercida durante o tempo de execução, geralmente por meios declarativos estáticos

Implementado frequentemente usando tipos primitivos padrão

Funções

Envio estático : função ou sub-rotina conhecida, específica no momento da compilação; é inequívoco e corresponde à assinatura

Implementado como funções estáticas; nenhum método pode ter a mesma assinatura

Despacho dinâmico : não é uma função ou sub-rotina específica em tempo de compilação; determinado pelo contexto durante a execução. Existem duas abordagens diferentes para o "envio dinâmico", distinguidas pelas informações contextuais usadas para selecionar a implementação da função apropriada.

No único [ dinâmico ] expedição , apenas o tipo da instância é utilizada para determinar a implementação da função apropriada. Nas linguagens de tipo estaticamente, o que isso significa na prática é que o tipo de instância decide qual implementação de método é usada, independentemente do tipo de referência indicado quando a variável é declarada / atribuída. Como apenas um único tipo - o tipo da instância do objeto - é usado para inferir a implementação apropriada, essa abordagem é chamada "despacho único".

Também há vários despachos [ dinâmicos ] , nos quais os tipos de parâmetros de entrada também ajudam a determinar qual implementação de função chamar. Como vários tipos - tanto o tipo da instância quanto o (s) tipo (s) do (s) parâmetro (s) - influenciam qual implementação de método é selecionada, essa abordagem é chamada de "despacho múltiplo".

Implementado como funções virtuais ou abstratas; outras pistas incluem métodos substituídos, ocultos ou sombreados.

NB: Se a sobrecarga de método envolve ou não despacho dinâmico, é específica do idioma. Por exemplo, em Java, os métodos sobrecarregados são enviados estaticamente.

Valores

Carregamento lento : estratégia de inicialização de objetos que adia a atribuição de valor até que seja necessário ; permite que um objeto esteja em um estado essencialmente válido, mas intencionalmente incompleto, e aguarde até que os dados sejam necessários antes de carregá-lo; geralmente é particularmente útil para carregar grandes conjuntos de dados ou aguardar recursos externos

Implementado frequentemente por propositalmente não carregar uma coleção ou lista em um objeto composto durante as chamadas de construtor ou inicialização até que algum chamador downstream solicite para ver o conteúdo dessa coleção (por exemplo, get_value_at, get_all_as, etc). As variações incluem carregar meta informações sobre a coleção (como tamanho ou chaves), mas omitir os dados reais; também fornece um mecanismo para alguns tempos de execução para fornecer aos desenvolvedores um esquema de implementação de singleton bastante seguro e eficiente

Carregamento Ansioso : estratégia de inicialização do objeto que executa imediatamente todas as atribuições de valor para que todos os dados precisem ser concluídos antes de se considerar em um estado válido.

Implementado frequentemente fornecendo aos objetos compostos todos os dados conhecidos o mais rápido possível, como durante uma chamada ou inicialização do construtor

Ligação de dados : geralmente envolve a criação de um link ou mapa ativo entre dois fluxos de informações compatíveis, para que as alterações em um sejam refletidas de volta no outro e vice-versa; para serem compatíveis, eles geralmente precisam ter um tipo de base ou interface comum

Implementado frequentemente como uma tentativa de fornecer uma sincronização mais limpa e consistente entre os diferentes aspectos do aplicativo (por exemplo, modelo de exibição para exibição, modelo para controlador etc.) e fala sobre conceitos como origem e destino, pontos de extremidade, ligação / desativação, atualização e eventos como on_bind, on_property_change, on_explicit, on_out_of_scope


NOTA DE EDIÇÃO: Última edição principal para fornecer uma descrição de exemplos de como isso ocorre com frequência. Exemplos de código específicos dependem inteiramente da implementação / tempo de execução / plataforma


2
Essa resposta parece específica demais para linguagens orientadas a objetos.
Jack

27

Qualquer coisa que seja decidida pelo compilador durante a compilação pode ser consultada na ligação EARLY / COMPILE TIME e qualquer coisa a ser decidida em RUNTIME é chamada de ligação LATE / RUNTIME .

Por exemplo,

Sobrecarga de método e substituição de método .

1) No Método Sobrecarregando o método, as chamadas para os métodos são decididas pelo compilador no sentido de que qual função será chamada é decidida pelo seu compilador no momento da compilação. Por isso, é uma ligação antecipada .

2) No método Substituição, é decidido em RUNTIME qual método será chamado. Por isso, é chamado de ATRASO ATRASADO .

Tentou mantê-lo simples e fácil de obter. Espero que isto ajude.


9

Associação tardia é quando o comportamento é avaliado em tempo de execução. É necessário quando você realmente deseja determinar como agir com base nas informações que você possui apenas quando o programa está sendo executado. O exemplo mais claro na minha opinião é o mecanismo de função virtual, especificamente em C ++.

class A
{
public:
    void f() {}
    virtual void g() {}
};

class B : public A
{
    void f() {}
    virtual void g() {}
};

int main()
{
    A* a = new B;
    a->f();
    a->g();
}

Neste exemplo, a->f()ele realmente chamará void A::f(), porque é vinculado antecipadamente (ou estaticamente) e, portanto, o programa em tempo de execução acha que é apenas um ponteiro para uma Avariável de tipo, enquanto a->g()que na verdade chamará void B::g(), porque o compilador, como g()é virtual, injeta código para parecer o endereço da função correta para chamar em tempo de execução.


1
"O tempo de execução"? Você está falando sobre C ++. O C ++ compila diretamente no código da máquina, não precisa de um tempo de execução para resolver métodos virtuais.
tdammers

3
@tdammers C ++ realmente precisa de uma biblioteca de tempo de execução, embora não seja para chamadas virtuais. Se você ler atentamente, notará que esta resposta diz que o compilador "injeta código para procurar o endereço da função correta [...] no tempo de execução".

Bem, mas esse "código para procurar o endereço da função correta" é basicamente apenas uma desreferência de ponteiro de dois estágios, independente do tipo, seguida por uma chamada de função. Não há "pensamento" envolvido; a única razão pela qual ele funciona de maneira confiável é porque o compilador faz a verificação do tipo no tempo de compilação ; no tempo de execução, o código gerado confia que o compilador executou a tarefa de verificação de tipo. Se você usar projeções inseguras (por exemplo , projeções de ponteiros no estilo C), poderá legalmente tratar objetos C ++ como objetos da classe errada, mas suas tabelas vt serão totalmente desarrumadas e o código será quebrado.
tdammers

@ Tdammers Tentei ficar longe desse tipo de resposta, porque é um detalhe de implementação de compiladores, o que pode ou não ser verdadeiro para algum compilador esotérico. O que importa é o conceito.
Yam Marcovic #

1
@tdammers E com "o tempo de execução" quero dizer "o programa em tempo de execução". Obviamente, C ++ não é gerenciado. Mas como você me mostrou que isso pode causar confusão, estou mudando para o texto completo.
Yam Marcovic #

5

se você estiver familiarizado com os ponteiros de função, este seria um exemplo. Pode-se dizer que as funções definidas são vinculativas antecipadas. enquanto que se você usar ponteiros de função, sua ligação tardia.

  int add(int x,int y)
  {
    return x+y;
  }
  int sub(int x,int y)
  {
      return x-y;
  }


    int main()
    {
     //get user choice
     int(*fp)(int,int);
     //if add
      fp=add;
     //else if sub
     fp=sub;
     cout<<fp(2,2);
    }

aqui funções add e sub são funções (seu endereço é vinculado no compilador de tempo de compilação)

mas o ponteiro da função está atrasado na ligação, o fp pode chamar add ou sub, dependendo da escolha do usuário [em tempo de execução].


3

A ligação antecipada e tardia só faz sentido no contexto dos tipos e não da maneira que você a está descrevendo. Praticamente todas as linguagens modernas são digitadas no sentido de que todos os valores têm tipos fixos. A diferença ocorre quando analisamos linguagens dinamicamente versus estaticamente tipadas. Em linguagens digitadas dinamicamente, as variáveis ​​não têm tipos, portanto, podem se referir a valores de qualquer tipo, e isso significa que, quando você chama um método em um objeto referido por alguma variável, a única maneira de determinar se essa chamada é válida ou não é: procure a classe para o objeto e veja se esse método realmente existe. Isso permite coisas interessantes, como adicionar novos métodos às classes em tempo de execução, porque a pesquisa real do método é adiada até o último momento. A maioria das pessoas chama esse estado de coisas de vinculativo tardio.

Em uma linguagem de tipo estaticamente, as variáveis ​​têm tipos e, uma vez declaradas, não podem se referir a nenhum valor que não seja do mesmo tipo. Isso não é estritamente verdade, mas vamos assumi-lo por enquanto. Agora, se você souber que a variável sempre se referirá a valores de um tipo específico, não há motivo para descobrir se uma chamada de método é válida ou não no tempo de execução, pois é possível determinar a validade antes da execução do código. Isso é chamado de ligação antecipada.

Um exemplo para demonstrar a ligação tardia em ruby:

a = 1 # a is an integer at this point
a.succ # asking for its successor is valid

class A
  def method_a
    # some code
  end
end

a = A.new
a.method_a # this is also valid
a.succ # this is not valid


class A # we can re-open the class and add a method
  def succ
    # some more code
  end
end
a.succ # now this is valid

A sequência de ações acima não é possível em uma linguagem como Java, na qual todos os tipos são corrigidos no tempo de execução.


1

Em vez de fornecer uma definição acadêmica, tentarei mostrar algumas das diferenças usando um exemplo do mundo real usando o VBA:

Ligação antecipada:

Dim x As FileSystemObject
Set x = New FileSystemObject
Debug.Print x.GetSpecialFolder(0)

Isso requer que uma referência seja definida como o componente "Microsoft Scripting Runtime" em tempo de design . Tem a vantagem de você já receber uma mensagem de erro no momento da compilação quando possui um erro de digitação FileSystemObjectou nomes de métodos como GetSpecialFolder.

Ligação tardia

Dim x As Object
Set x = CreateObject("Scripting.FileSystemObject")
Debug.Print x.GetSpecialFolder(0)

Isso não requer que uma referência seja definida antecipadamente, a criação da instância e a determinação do tipo ocorrerão apenas no tempo de execução. O compilador não reclamará no momento da compilação quando você tentar chamar um método inexistente x, isso levará a um erro em tempo de execução somente quando a linha específica for executada.

Portanto, a desvantagem da ligação tardia é que você não tem nenhuma verificação de tipo forte aqui. Mas essa também é a vantagem - digamos que você tenha um componente em que existem várias versões e cada versão mais nova fornece algumas funções adicionais. (Um exemplo do mundo real são os componentes do MS Office, como a interface COM do Excel) A ligação tardia permite que você escreva um código que funcione em conjunto com todas essas versões - você pode primeiro determinar a versão específica do componente e se descobrir que possui apenas uma versão mais antiga disponível, evite executar chamadas de funções que não funcionam com essa versão.


-2

Talvez o exemplo mais comum de ligação tardia seja a resolução de URLs da Internet. Ele suporta sistemas dinâmicos e grandes sistemas sem tentar vincular e vincular todos os sites do mundo antes que você possa acessá-los, mas, por outro lado, incorre em alguma sobrecarga (pesquisa de DNS, muito menos roteamento de IP) em tempo de execução.

Por essa luz, a maioria das variedades de encadernação em ambientes de idiomas é mais ou menos cedo, no momento da compilação ou no tempo do link.

Cada tipo tem custos e benefícios.


Você pode localizar uma referência para esta definição de ligação? Eu não ouvi falar da resolução de endereços da Internet como "vinculativo", embora, como o vínculo seja o ato de resolver nomes, suponho que alguém tenha argumentado que o conceito de ligação antecipada / tardia pode ser aplicado à resolução de URI para endereços da Internet. Mas essa não é uma interpretação comum, e o conceito de ligação antecipada / tardia antecede o tempo em que os computadores eram comumente conectados à Internet.
Jay Elston
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.