Estou trabalhando com a memória de alguns lambdas em C ++, mas estou um pouco confuso com o tamanho deles.
Aqui está meu código de teste:
#include <iostream>
#include <string>
int main()
{
auto f = [](){ return 17; };
std::cout << f() << std::endl;
std::cout << &f << std::endl;
std::cout << sizeof(f) << std::endl;
}
Você pode executá-lo aqui: http://fiddle.jyt.io/github/b13f682d1237eb69ebdc60728bb52598
A saída é:
17
0x7d90ba8f626f
1
Isso sugere que o tamanho do meu lambda é 1.
Como isso é possível?
O lambda não deveria ser, no mínimo, um ponteiro para sua implementação?
struct
com umoperator()
)Respostas:
O lambda em questão, na verdade, não tem estado .
Examinar:
E se tivéssemos
lambda f;
, é uma classe vazia. Não só o anterior élambda
funcionalmente semelhante ao seu lambda, mas (basicamente) como o seu lambda é implementado! (Ele também precisa de uma conversão implícita para o operador de ponteiro de função, e o nomelambda
será substituído por algum pseudo-guid gerado pelo compilador)Em C ++, os objetos não são ponteiros. Eles são coisas reais. Eles só usam o espaço necessário para armazenar os dados neles. Um ponteiro para um objeto pode ser maior do que um objeto.
Embora você possa pensar nesse lambda como um ponteiro para uma função, não é. Você não pode reatribuir o
auto f = [](){ return 17; };
a uma função diferente ou lambda!o acima é ilegal . Não há espaço
f
para armazenar qual função será chamada - as informações são armazenadas no tipo def
, não no valor def
!Se você fez isso:
ou isto:
você não está mais armazenando o lambda diretamente. Em ambos os casos,
f = [](){ return -42; }
é legal - por isso, nestes casos, estamos armazenando qual função estamos invocando no valorf
. Esizeof(f)
não é mais1
, mas simsizeof(int(*)())
ou maior (basicamente, seja do tamanho de um ponteiro ou maior, como você espera.std::function
Tem um tamanho mínimo implícito pelo padrão (eles devem ser capazes de armazenar "dentro de si" chamáveis até um certo tamanho) que é pelo menos tão grande quanto um ponteiro de função na prática).Nesse
int(*f)()
caso, você está armazenando um ponteiro de função para uma função que se comporta como se você chamasse aquele lambda. Isso só funciona para lambdas sem estado (aqueles com uma[]
lista de captura vazia ).No
std::function<int()> f
caso, você está criando umastd::function<int()>
instância de classe de eliminação de tipo que (neste caso) usa a colocação new para armazenar uma cópia do lambda de tamanho 1 em um buffer interno (e, se um lambda maior foi passado (com mais estado ), usaria alocação de heap).Como um palpite, algo assim é provavelmente o que você acha que está acontecendo. Que um lambda é um objeto cujo tipo é descrito por sua assinatura. Em C ++, foi decidido fazer lambdas abstrações de custo zero sobre a implementação manual do objeto de função. Isso permite que você passe um lambda para um
std
algoritmo (ou semelhante) e tenha seu conteúdo totalmente visível para o compilador quando ele instancia o modelo do algoritmo. Se um lambda tivesse um tipo comostd::function<void(int)>
, seu conteúdo não seria totalmente visível e um objeto de função feito à mão poderia ser mais rápido.O objetivo da padronização do C ++ é a programação de alto nível com zero overhead em relação ao código C feito à mão.
Agora que você entende que,
f
na verdade, você não tem estado, deve haver outra pergunta em sua cabeça: o lambda não tem estado. Por que não tem tamanho0
?Essa é a resposta curta.
Todos os objetos em C ++ devem ter um tamanho mínimo de 1 sob o padrão e dois objetos do mesmo tipo não podem ter o mesmo endereço. Eles estão conectados, porque um array do tipo
T
terá os elementossizeof(T)
separados.Agora, como não tem estado, às vezes não pode ocupar espaço. Isso não pode acontecer quando está "sozinho", mas em alguns contextos pode acontecer.
std::tuple
e um código de biblioteca semelhante explora esse fato. É assim que funciona:Como um lambda é equivalente a uma classe com
operator()
sobrecarregados, lambdas sem estado (com uma[]
lista de captura) são classes vazias. Eles têmsizeof
de1
. Na verdade, se você herdar deles (o que é permitido!), Eles não ocuparão espaço , desde que não causem uma colisão de endereços do mesmo tipo . (Isso é conhecido como otimização de base vazia).o
sizeof(make_toy( []{std::cout << "hello world!\n"; } ))
issizeof(int)
(bem, o acima é ilegal porque você não pode criar um lambda em um contexto não avaliado: você precisa criar um nomeado eauto toy = make_toy(blah);
depois fazersizeof(blah)
, mas isso é apenas ruído).sizeof([]{std::cout << "hello world!\n"; })
ainda é1
(qualificações semelhantes).Se criarmos outro tipo de brinquedo:
isso tem duas cópias do lambda. Como não podem compartilhar o mesmo endereço,
sizeof(toy2(some_lambda))
é2
!fonte
()
adicionado.Um lambda não é um ponteiro de função.
Um lambda é uma instância de uma classe. Seu código é aproximadamente equivalente a:
A classe interna que representa um lambda não tem membros de classe, portanto,
sizeof()
é 1 (não pode ser 0, por razões adequadamente declaradas em outro lugar ).Se seu lambda capturar algumas variáveis, elas serão equivalentes aos membros da classe e você
sizeof()
indicará de acordo.fonte
sizeof()
não pode ser 0?Seu compilador traduz mais ou menos o lambda para o seguinte tipo de estrutura:
Como essa estrutura não tem membros não estáticos, ela tem o mesmo tamanho de uma estrutura vazia, que é
1
.Isso muda assim que você adiciona uma lista de captura não vazia ao seu lambda:
Que irá traduzir para
Como a estrutura gerada agora precisa armazenar um
int
membro não estático para a captura, seu tamanho aumentará parasizeof(int)
. O tamanho continuará crescendo conforme você captura mais coisas.(Por favor, considere a analogia da estrutura com um grão de sal. Embora seja uma boa maneira de raciocinar sobre como os lambdas funcionam internamente, esta não é uma tradução literal do que o compilador fará)
fonte
Não necessariamente. De acordo com o padrão, o tamanho da classe única e sem nome é definido pela implementação . Trecho de [expr.prim.lambda] , C ++ 14 (ênfase minha):
No seu caso - para o compilador que você usa - você obtém o tamanho 1, o que não significa que seja fixo. Ele pode variar entre as diferentes implementações do compilador.
fonte
De http://en.cppreference.com/w/cpp/language/lambda :
De http://en.cppreference.com/w/cpp/language/sizeof
fonte