Pergunta

Estou apenas aprendendo agora sobre ponteiros de função e, como eu estava lendo o capítulo K & R sobre o assunto, a primeira coisa que me bateu foi: "Ei, isso é um bocado como um fechamento." Eu sabia que essa suposição é fundamentalmente errado de alguma forma e depois de uma pesquisa on-line eu não acho realmente qualquer análise desta comparação.

Então, por que são ponteiros de função de estilo C fundamentalmente diferentes de encerramentos ou lambdas? Tanto quanto eu posso dizer que tem a ver com o fato de que o ponteiro de função ainda aponta para uma função definida (nomeado), em oposição à prática da anonimamente definição da função.

Por que está passando uma função a uma função visto como mais poderoso no segundo caso, onde é sem nome, que o primeiro onde ele é apenas uma função normal, todos os dias que está sendo passado?

Por favor me diga como e por que estou errado para comparar os dois tão de perto.

Graças.

Foi útil?

Solução

Um lambda (ou fecho ) incorpora tanto o ponteiro de função e variáveis. É por isso que, em C #, você pode fazer:

int lessThan = 100;
Func<int, bool> lessThanTest = delegate(int i) {
   return i < lessThan;
};

I utilizado um delegado anónimo lá como uma tampa (a sua sintaxe é um pouco mais clara e mais perto de C do que o equivalente de lambda), que capturou lessThan (uma variável de pilha) para o fecho. Quando o fecho é avaliada, lessThan (cujo quadro de pilha pode ter sido destruído) continuará a ser referenciada. Se eu mudar lessThan, então eu mudo a comparação:

int lessThan = 100;
Func<int, bool> lessThanTest = delegate(int i) {
   return i < lessThan;
};

lessThanTest(99); // returns true
lessThan = 10;
lessThanTest(99); // returns false

Em C, isso seria ilegal:

BOOL (*lessThanTest)(int);
int lessThan = 100;

lessThanTest = &LessThan;

BOOL LessThan(int i) {
   return i < lessThan; // compile error - lessThan is not in scope
}

embora eu poderia definir um ponteiro de função que leva 2 argumentos:

int lessThan = 100;
BOOL (*lessThanTest)(int, int);

lessThanTest = &LessThan;
lessThanTest(99, lessThan); // returns true
lessThan = 10;
lessThanTest(100, lessThan); // returns false

BOOL LessThan(int i, int lessThan) {
   return i < lessThan;
}

Mas, agora eu tenho que passar os 2 argumentos quando eu avaliá-lo. Se eu quisesse passar este ponteiro de função para outra função onde lessThan não estava no escopo, eu teria que mantê-lo manualmente vivo passando-a para cada função na cadeia, ou promovê-lo a um global.

Embora a maioria das línguas tradicionais que os fechamentos de apoio usar funções anônimas, não há nenhuma exigência para isso. Você pode ter fechamentos sem funções anônimas e funções anônimas, sem encerramentos.

Resumo:. Um fecho é uma combinação da função de ponteiro + variáveis ??capturados

Outras dicas

Como alguém que tem escrito compiladores para linguagens com e sem fechamentos 'reais', eu respeitosamente discordo de algumas das respostas acima. A Lisp, Scheme, ML, ou encerramento Haskell não cria uma nova função dinamicamente . Em vez disso, reutiliza uma função existente , mas fá-lo com novas variáveis ??livres . A coleção de variáveis ??livres é muitas vezes chamado o ambiente , pelo menos por teóricos da linguagem de programação.

Um fecho é apenas um agregado contendo uma função e um ambiente. No ML padrão de compilador New Jersey, que representou um como um registro; um campo continha um ponteiro para o código, e os outros campos contidos os valores das variáveis ??livres. O compilador criado um novo fechamento (não função) de forma dinâmica através da atribuição de um novo registro que contém um ponteiro para os mesmos código, mas com diferentes valores para as variáveis ??livres.

Você pode simular tudo isso em C, mas é um pé no saco. Duas técnicas são populares:

  1. Passar por um ponteiro para a função (o código) e um ponteiro separado para as variáveis ??livres, de modo que o fecho é dividida em duas variáveis ??C.

  2. Passar por um ponteiro para uma estrutura, onde a estrutura contém os valores das variáveis ??livres e também um ponteiro para o código.

Técnica # 1 é ideal quando você está tentando simular algum tipo de polimorfismo em C e você não quer revelar o tipo de ambiente --- você usar um ponteiro void * para representam o meio ambiente. Para exemplos, olhada de Dave Hanson C Interfaces e Implementações . Técnica # 2, que se assemelha mais de perto o que acontece em compiladores de código nativo para linguagens funcionais, também se assemelha a uma outra técnica familiar ... C ++ objetos com funções membro virtuais. As implementações são quase idênticas.

Esta observação levou a uma piada de Henry Baker:

As pessoas no mundo Algol / Fortran queixou-se há anos que eles não entenderam o que possíveis fechamentos de função uso teria na programação eficiente do futuro. Em seguida, a revolução `programação orientada a objeto' aconteceu, e agora programas todos usando fechamentos de função, exceto que eles ainda se recusam a chamá-los assim.

Em C você não pode definir a função inline, então você não pode realmente criar um fechamento. Tudo o que você está fazendo está passando em torno de uma referência a algum método pré-definido. Em linguagens que suportam métodos anônimos / encerramentos, a definição dos métodos são muito mais flexível.

Em termos mais simples, ponteiros de função não têm escopo associado com eles (a menos que você contar o escopo global), enquanto fechos incluem o escopo do método que é defini-los. Com lambdas, você pode escrever um método que escreve um método. Closures permitem ligar "alguns argumentos para uma função e recebendo uma função inferior arity como resultado." (Retirado o comentário de Thomas). Você não pode fazer isso em C.

EDIT: Adicionando um exemplo (eu vou usar causa sintaxe Actionscript-ish que é o que está na minha mente agora):

Digamos que você tenha algum método que leva um outro método como seu argumento, mas não fornece uma maneira de passar qualquer parâmetro para esse método quando ele é chamado? Como, digamos, algum método que provoca um atraso antes de executar o método que você passou (exemplo estúpido, mas eu quero mantê-lo simples).

function runLater(f:Function):Void {
  sleep(100);
  f();
}

Agora dizer que você quer runLater utilizador () para atrasar algum processamento de um objeto:

function objectProcessor(o:Object):Void {
  /* Do something cool with the object! */
}

function process(o:Object):Void {
  runLater(function() { objectProcessor(o); });
}

A função que você está passando para processo () não é algum staticly função definida mais. É gerado dinamicamente, e é capaz de incluir referências a variáveis ??que estavam no escopo quando o método foi definido. Assim, ele pode acessar 'o' e 'objectProcessor', embora aqueles que não estão no escopo global.

Espero que fazia sentido.

Encerramento = lógica + ambiente.

Por exemplo, considere este método C # 3:

public Person FindPerson(IEnumerable<Person> people, string name)
{
    return people.Where(person => person.Name == name);
}

A expressão lambda não apenas encapsula a lógica ( "comparar o nome"), mas também o meio ambiente, incluindo o parâmetro (ou seja variável local) "nome".

Para saber mais sobre isso, ter um olhar para o meu sobre o encerramento que o leva através de C # 1, 2 e 3, mostrando como fechamentos de facilitar as coisas.

Em C, ponteiros de função podem ser passados ??como argumentos para funções e retornados como valores de funções, mas existem apenas funções ao mais alto nível: você não pode definições de funções ninho dentro de si. Pense sobre o que seria necessário para C para suportar funções aninhadas que podem acessar as variáveis ??da função externa, enquanto ainda está sendo capaz de enviar ponteiros de função para cima e para baixo da pilha de chamadas. (Para seguir esta explicação, você deve saber o básico de como chamadas de função são implementados em C ea maioria das línguas semelhantes: browse através do entrada pilha de chamadas na Wikipedia.)

Que tipo de objeto é um ponteiro para uma função aninhada? Não pode ser apenas o endereço do código, porque se você chamá-lo, como ele acessar as variáveis ??da função externa? (Lembre-se que por causa de recursão, pode haver várias chamadas diferentes da função externa ativa ao mesmo tempo.) Isso é chamado de funarg problema , e há dois subproblemas:. o problema funargs para baixo e para cima o problema funargs

As baixo funargs problema, ou seja, o envio de um ponteiro de função "para baixo da pilha" como um argumento para uma função que você chama, na verdade não é incompatível com C, e GCC suportes funções aninhadas como funargs para baixo. No GCC, quando você cria um ponteiro para uma função aninhada, você realmente obter um ponteiro para um trampolim , uma peça construída de forma dinâmica de código que configura o link estática ponteiro e, em seguida, chama a função real, que usa o vínculo estático ponteiro para o acesso as variáveis da função externa.

As cima funargs problema é mais difícil. O GCC não impedi-lo de deixar a exist ponteiro trampolim após a função externa não está mais ativo (não tem registro na pilha de chamadas), e, em seguida, o ponteiro vínculo estático poderia apontar para o lixo. registros de ativação não pode mais ser alocados em uma pilha. A solução usual é de atribuir-lhes na pilha, e que um objeto de função que representa uma função aninhada apenas aponte para o registo de activação da função exterior. Tal objeto é chamado de fechamento . Em seguida, a língua normalmente têm para apoiar coleta de lixo para que os registros podem ser libertada uma vez que não há mais ponteiros apontando para eles.

Lambdas ( funções anônimas ) são realmente uma questão separada, mas geralmente uma linguagem que permite definir funções anônimas na mosca também vai deixar você devolvê-los como valores de função, então eles acabam sendo encerramentos.

A lambda é um anônimo, função definida dinamicamente. Você simplesmente não pode fazer isso em C ... como para encerramentos (ou o convination dos dois), o exemplo típico lisp seria algo ao longo das linhas de:

(defun get-counter (n-start +-number)
     "Returns a function that returns a number incremented
      by +-number every time it is called"
    (lambda () (setf n-start (+ +-number n-start))))

termos em C, pode-se dizer que o ambiente lexical (pilha) de get-counter está sendo capturado pela função anônima, e modificado internamente como os seguintes exemplos de espectáculos:

[1]> (defun get-counter (n-start +-number)
         "Returns a function that returns a number incremented
          by +-number every time it is called"
        (lambda () (setf n-start (+ +-number n-start))))
GET-COUNTER
[2]> (defvar x (get-counter 2 3))
X
[3]> (funcall x)
5
[4]> (funcall x)
8
[5]> (funcall x)
11
[6]> (funcall x)
14
[7]> (funcall x)
17
[8]> (funcall x)
20
[9]> 

Closures implica alguma variável do ponto de definição da função está ligada em conjunto com a lógica da função, como ser capaz de declarar um mini-objeto na mosca.

Um problema importante com C e fechamentos é variáveis ??alocados na pilha será destruída ao sair do escopo atual, independentemente de se um fecho estava apontando para eles. Isso levaria ao tipo de erros as pessoas ficam quando descuidadamente retornar ponteiros para variáveis ??locais. Closures basicamente implica todas as variáveis ??relevantes são ou ref-contados ou itens de coleta de lixo em um heap.

Não me sinto confortável lambda equiparação com fecho, porque eu não tenho certeza de que lambdas em todas as línguas são fechamentos, às vezes eu acho que lambdas acabam de ser definidos localmente funções anônimas, sem a ligação de variáveis ??(Python pré 2,1?).

Em GCC é possível funções lambda simulados utilizando a seguinte macro:

#define lambda(l_ret_type, l_arguments, l_body)       \
({                                                    \
    l_ret_type l_anonymous_functions_name l_arguments \
    l_body                                            \
    &l_anonymous_functions_name;                      \
})

Exemplo de fonte :

qsort (array, sizeof (array) / sizeof (array[0]), sizeof (array[0]),
     lambda (int, (const void *a, const void *b),
             {
               dump ();
               printf ("Comparison %d: %d and %d\n",
                       ++ comparison, *(const int *) a, *(const int *) b);
               return *(const int *) a - *(const int *) b;
             }));

Usando esta técnica, claro, elimina a possibilidade de sua aplicação trabalhar com outros compiladores e aparentemente está "indefinido" comportamento de modo YMMV.

fechamento captura o variáveis ??livres em um ambiente . O ambiente continuará a existir, mesmo que o código circundante não pode estar ativa.

Um exemplo em Lisp comum, onde MAKE-ADDER retorna um novo fechamento.

CL-USER 53 > (defun make-adder (start delta) (lambda () (incf start delta)))
MAKE-ADDER

CL-USER 54 > (compile *)
MAKE-ADDER
NIL
NIL

Usando a função acima:

CL-USER 55 > (let ((adder1 (make-adder 0 10))
                   (adder2 (make-adder 17 20)))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder2))
               (print (funcall adder2))
               (print (funcall adder2))
               (print (funcall adder1))
               (print (funcall adder1))
               (describe adder1)
               (describe adder2)
               (values))

10 
20 
30 
40 
37 
57 
77 
50 
60 
#<Closure 1 subfunction of MAKE-ADDER 4060001ED4> is a CLOSURE
Function         #<Function 1 subfunction of MAKE-ADDER 4060001CAC>
Environment      #(60 10)
#<Closure 1 subfunction of MAKE-ADDER 4060001EFC> is a CLOSURE
Function         #<Function 1 subfunction of MAKE-ADDER 4060001CAC>
Environment      #(77 20)

Note que a função DESCRIBE mostra que o função de objetos para ambos fechamento são os mesmos, mas o ambiente é diferente.

Common Lisp torna ambos encerramentos e objetos de função pura (aqueles sem um ambiente) tanto para ser funções e pode-se chamar ambos no mesmo caminho, aqui usando FUNCALL.

A principal diferença surge a partir da falta de definição do âmbito lexical em C.

Um ponteiro de função é apenas isso, um ponteiro para um bloco de código. Qualquer variável não-pilha que faz referência é global, estático ou similar.

Um fechamento, OTOH, tem seu próprio estado na forma de 'variáveis ??externas', ou 'upvalues'. eles podem ser tão privado ou compartilhado como você quer, usando o escopo lexical. Você pode criar lotes de fechamento com o mesmo código de função, mas casos diferentes variáveis.

Alguns fechamentos podem compartilhar algumas variáveis, e por isso pode ser a interface de um objeto (no sentido OOP). Para que isso em C você tem que associar uma estrutura com uma tabela de ponteiros de função (que é o C ++ faz, com uma vtable classe).

Em suma, um fechamento é um ponteiro de função PLUS algum estado. é uma construção de nível superior

A maioria das respostas indicam que os encerramentos exigem ponteiros de função, possivelmente para funções anônimas, mas como Mark escreveu fechamentos pode existir com funções nomeadas. Aqui está um exemplo em Perl:

{
    my $count;
    sub increment { return $count++ }
}

O encerramento é o ambiente que define a variável $count. Ele está disponível apenas para a sub-rotina increment e persiste entre as chamadas.

Em C um ponteiro de função é um ponteiro que irá chamar uma função quando você desreferenciava-lo, um fechamento é um valor que contém a lógica de uma função e para o ambiente (variáveis ??e os valores que eles são obrigados a) e um lambda geralmente se refere a um valor que é realmente uma função sem nome. Em C uma função não é um primeiro valor de classe para que ele não pode ser passado em torno de modo que você tem que passar um ponteiro para ele em vez disso, no entanto, em linguagens funcionais (como Scheme) pode passar funções da mesma forma que você passar qualquer outro valor

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top