Podemos ter funções dentro de funções em C ++?


225

Quero dizer algo como:

int main() 
{
  void a() 
  {
      // code
  }
  a();

  return 0;
}

1
Por que você está tentando fazer isso? Explicar seu objetivo pode permitir que alguém lhe diga o caminho certo para atingir seu objetivo.
Thomas Owens

3
O gcc suporta funções aninhadas como uma extensão não padrão. Mas é melhor não usá-lo, mesmo se você estiver usando o gcc. E no modo C ++, ele não está disponível de qualquer maneira.
Sven Marnach

27
@ Thomas: Porque seria bom reduzir o escopo de um? Funções em funções é um recurso comum em outros idiomas.
Johan Kotlinski

64
Ele está falando sobre funções aninhadas. Da mesma forma que ele é capaz de fazer as próximas aulas dentro das classes, ele deseja aninhar uma função dentro de uma função. Na verdade, tive situações em que também o teria feito, se fosse possível. Existem linguagens (por exemplo, F #) que permitem isso, e posso dizer que ele pode tornar o código muito mais claro, legível e sustentável, sem poluir uma biblioteca com dezenas de funções auxiliares que são inúteis fora de um contexto muito específico. ;)
Mephane

16
@Thomas - funções aninhados podem ser um excelente mecanismo para quebrar complexos funções / algoritmos sem sem preencher o âmbito actual com funções que são não de uso geral no âmbito envolvente. Pascal e Ada têm um apoio adorável (IMO) para eles. O mesmo acontece com Scala e muitas outras antigas / novas línguas respeitadas. Como qualquer outro recurso, eles também podem ser abusados, mas isso é uma função do desenvolvedor. IMO, eles têm sido muito mais benéficos do que prejudiciais.
Luis.espinal

Respostas:


271

C ++ moderno - Sim com lambdas!

Nas versões atuais do c ++ (C ++ 11, C ++ 14 e C ++ 17), você pode ter funções dentro de funções na forma de um lambda:

int main() {
    // This declares a lambda, which can be called just like a function
    auto print_message = [](std::string message) 
    { 
        std::cout << message << "\n"; 
    };

    // Prints "Hello!" 10 times
    for(int i = 0; i < 10; i++) {
        print_message("Hello!"); 
    }
}

Lambdas também pode modificar variáveis ​​locais através de ** captura por referência *. Com captura por referência, o lambda tem acesso a todas as variáveis ​​locais declaradas no escopo do lambda. Pode modificá-los e alterá-los normalmente.

int main() {
    int i = 0;
    // Captures i by reference; increments it by one
    auto addOne = [&] () {
        i++; 
    };

    while(i < 10) {
        addOne(); //Add 1 to i
        std::cout << i << "\n";
    }
}

C ++ 98 e C ++ 03 - Não diretamente, mas sim com funções estáticas nas classes locais

C ++ não suporta isso diretamente.

Dito isto, você pode ter classes locais, e elas podem ter funções (não staticou static), para que você possa estender isso até certo ponto, embora seja um pouco complicado:

int main() // it's int, dammit!
{
  struct X { // struct's as good as class
    static void a()
    {
    }
  };

  X::a();

  return 0;
}

No entanto, eu questionaria a práxis. Todo mundo sabe (bem, agora que você faz :)) de qualquer maneira, o C ++ não suporta funções locais, então elas estão acostumadas a não tê-las. Eles não estão acostumados, no entanto, a esse argumento. Eu gastaria um bom tempo nesse código para garantir que ele realmente esteja lá apenas para permitir funções locais. Não é bom.


3
Main também leva dois argumentos se você for pedante sobre o tipo de retorno. :) (Ou isso é opcional, mas não o retorno estes dias eu não posso manter-se?.)
Leo Davidson

3
Isso é ruim - quebra todas as convenções de código bom e limpo. Não consigo pensar em uma única instância em que essa seja uma boa ideia.
Thomas Owens

19
@ Thomas Owens: É bom se você precisar de uma função de retorno de chamada e não quiser poluir algum outro espaço de nome com ela.
Leo Davidson

9
@ Leo: O padrão diz que existem duas formas permitidas para main: int main()eint main(int argc, char* argv[])
John Dibling

8
O padrão diz int main()e int main(int argc, char* argv[])deve ser suportado e outros podem ser suportados, mas todos têm retorno int.
JOEG

260

Para todos os efeitos, o C ++ suporta isso via lambdas : 1

int main() {
    auto f = []() { return 42; };
    std::cout << "f() = " << f() << std::endl;
}

Aqui, fé um objeto lambda que atua como uma função local no main. As capturas podem ser especificadas para permitir que a função acesse objetos locais.

Nos bastidores, fé um objeto de função (ou seja, um objeto de um tipo que fornece um operator()). O tipo de objeto da função é criado pelo compilador com base no lambda.


1 desde C ++ 11


5
Ah, isso é legal! Eu não pensei nisso. Isso é muito melhor do que minha ideia, +1de mim.
S1

1
@sbi: Na verdade, usei estruturas locais para simular isso no passado (sim, tenho vergonha de mim mesmo). Mas a utilidade é limitada pelo fato de que estruturas locais não criam um fechamento, ou seja, você não pode acessar variáveis ​​locais nelas. Você precisa passar e armazená-los explicitamente através de um construtor.
Konrad Rudolph

1
@ Konrad: Outro problema com eles é que no C ++ 98 você não deve usar tipos locais como parâmetros de modelo. Eu acho que C ++ 1x levantou essa restrição, no entanto. (Ou era que C ++ 03?)
SBI

3
@ Luis: Eu devo concordar com Fred. Você está anexando um significado às lambdas que elas simplesmente não têm (nem em C ++ nem em outras linguagens com as quais trabalhei - que não incluem Python e Ada, para o registro). Além disso, fazer essa distinção não é significativa no C ++ porque o C ++ não possui funções locais, ponto final. Só tem lambdas. Se você deseja limitar o escopo de algo semelhante a uma função, suas únicas opções são lambdas ou a estrutura local mencionada em outras respostas. Eu diria que o último é complicado demais para ter algum interesse prático.
Konrad Rudolph

2
@AustinWBryan Não, lambdas em C ++ são apenas açúcar sintático para functores e têm zero de sobrecarga. Há uma pergunta com mais detalhes em algum lugar deste site.
Konrad Rudolph

42

As classes locais já foram mencionadas, mas aqui está uma maneira de deixá-las aparecer ainda mais como funções locais, usando uma sobrecarga de operador () e uma classe anônima:

int main() {
    struct {
        unsigned int operator() (unsigned int val) const {
            return val<=1 ? 1 : val*(*this)(val-1);
        }
    } fac;

    std::cout << fac(5) << '\n';
}

Eu não aconselho a usar isso, é apenas um truque engraçado (pode fazer, mas eu não deveria).


Atualização de 2014:

Com a ascensão do C ++ 11 há um tempo, agora você pode ter funções locais cuja sintaxe lembra um pouco o JavaScript:

auto fac = [] (unsigned int val) {
    return val*42;
};

1
Deve estar operator () (unsigned int val)faltando um par de parênteses.
Joe D

1
Na verdade, isso é perfeitamente razoável se você precisar passar esse functor para uma função ou algoritmo stl, como std::sort(), ou std::for_each().
Dima

1
@ Dima: Infelizmente, em C ++ 03, tipos definidos localmente não podem ser usados ​​como argumentos de modelo. O C ++ 0x corrige isso, mas também fornece soluções muito melhores de lambdas, então você ainda não faria isso.
Ben Voigt

Opa, você está certo. Foi mal. Mas, ainda assim, este não é apenas um truque engraçado. Teria sido uma coisa útil se fosse permitido. :)
Dima

3
Recursão é suportada. No entanto, você não pode usar autopara declarar a variável. Stroustrup dá o exemplo: function<void(char*b, char*e)> rev=[](char*b, char*e) { if( 1<e-b ) { swap( *b, *--e); rev(++b,e); } };para reverter uma string com indicadores de início e fim.
epónimo

17

Não.

O que você está tentando fazer?

Gambiarra:

int main(void)
{
  struct foo
  {
    void operator()() { int a = 1; }
  };

  foo b;
  b(); // call the operator()

}

2
Observe que a abordagem de instanciação de classe vem com uma alocação de memória e, portanto, é dominada pela abordagem estática.
ManuelSchneid3

14

A partir do C ++ 11, você pode usar lambdas apropriadas . Veja as outras respostas para mais detalhes.


Resposta antiga: Você pode, mais ou menos, mas precisa trapacear e usar uma classe falsa:

void moo()
{
    class dummy
    {
    public:
         static void a() { printf("I'm in a!\n"); }
    };

    dummy::a();
    dummy::a();
}

Não tenho certeza de que você pode, exceto criando um objeto (que adiciona o mesmo ruído, IMO). A menos que haja algo inteligente que você possa fazer com espaços para nome, mas não consigo pensar nisso e provavelmente não é uma boa ideia abusar do idioma mais do que já somos. :)
Leo Davidson

O livrar-se-do-manequim :: está em uma das outras respostas.
Sebastian Mach

8

Como já mencionado, você pode usar funções aninhadas usando as extensões de idioma gnu no gcc. Se você (ou seu projeto) aderir à cadeia de ferramentas gcc, seu código será mais portátil nas diferentes arquiteturas direcionadas pelo compilador gcc.

No entanto, se houver um requisito possível de que você possa precisar compilar código com uma cadeia de ferramentas diferente, eu ficaria longe dessas extensões.


Eu também andaria com cuidado ao usar funções aninhadas. Elas são uma solução bonita para gerenciar a estrutura de blocos de código complexos e ainda coesos (cujas partes não são para uso externo / geral). Eles também são muito úteis no controle da poluição do espaço para nome (uma preocupação muito real com a complexidade natural / classes longas em idiomas detalhados.)

Mas, como qualquer coisa, eles podem estar abertos a abusos.

É triste que o C / C ++ não suporte recursos como padrão. A maioria das variantes pascal e Ada fazem (quase todas as linguagens baseadas em Algol). Mesmo com JavaScript. Mesmo com línguas modernas como Scala. O mesmo acontece com linguagens veneráveis ​​como Erlang, Lisp ou Python.

E, assim como no C / C ++, infelizmente o Java (com o qual ganho a maior parte da minha vida) não.

Menciono Java aqui porque vejo vários pôsteres sugerindo o uso de classes e métodos de classe como alternativas às funções aninhadas. E essa também é a solução típica em Java.

Resposta curta: Não.

Fazer isso tende a introduzir uma complexidade artificial desnecessária em uma hierarquia de classes. Com todas as coisas iguais, o ideal é ter uma hierarquia de classes (e seus abrangentes namespaces e escopos) representando um domínio real o mais simples possível.

Funções aninhadas ajudam a lidar com a complexidade "particular", dentro da função. Na falta dessas instalações, deve-se evitar a propagação dessa complexidade "privada" para dentro do modelo de classe.

No software (e em qualquer disciplina de engenharia), a modelagem é uma questão de troca. Assim, na vida real, haverá exceções justificadas para essas regras (ou melhor, diretrizes). Continue com cuidado, no entanto.


8

Você não pode ter funções locais em C ++. No entanto, o C ++ 11 possui lambdas . Lambdas são basicamente variáveis ​​que funcionam como funções.

Um lambda tem o tipo std::function( na verdade isso não é bem verdade , mas na maioria dos casos você pode supor que sim). Para usar esse tipo, você precisa #include <functional>. std::functioné um modelo, tendo como argumento o tipo de retorno e os tipos de argumento, com a sintaxe std::function<ReturnType(ArgumentTypes). Por exemplo, std::function<int(std::string, float)>é um lambda retornando um inte recebendo dois argumentos, um std::stringe um float. O mais comum é std::function<void()>que nada retorna e não aceita argumentos.

Depois que um lambda é declarado, ele é chamado como uma função normal, usando a sintaxe lambda(arguments).

Para definir um lambda, use a sintaxe [captures](arguments){code}(existem outras maneiras de fazer isso, mas não vou mencioná-las aqui). argumentssão os argumentos que o lambda usa e codeé o código que deve ser executado quando o lambda é chamado. Geralmente você coloca [=]ou [&]como captura. [=]significa que você captura todas as variáveis ​​no escopo em que o valor é definido por valor, o que significa que elas manterão o valor que tinham quando o lambda foi declarado. [&]significa que você captura todas as variáveis ​​no escopo por referência, o que significa que elas sempre terão seu valor atual, mas se forem apagadas da memória, o programa falhará. aqui estão alguns exemplos:

#include <functional>
#include <iostream>

int main(){
    int x = 1;

    std::function<void()> lambda1 = [=](){
        std::cout << x << std::endl;
    };
    std::function<void()> lambda2 = [&](){
        std::cout << x << std::endl;
    };

    x = 2;
    lambda1();    //Prints 1 since that was the value of x when it was captured and x was captured by value with [=]
    lambda2();    //Prints 2 since that's the current value of x and x was captured by value with [&]

    std::function<void()> lambda3 = [](){}, lambda4 = [](){};    //I prefer to initialize these since calling an uninitialized lambda is undefined behavior.
                                                                 //[](){} is the empty lambda.

    {
        int y = 3;    //y will be deleted from the memory at the end of this scope
        lambda3 = [=](){
            std::cout << y << endl;
        };
        lambda4 = [&](){
            std::cout << y << endl;
        };
    }

    lambda3();    //Prints 3, since that's the value y had when it was captured

    lambda4();    //Causes the program to crash, since y was captured by reference and y doesn't exist anymore.
                  //This is a bit like if you had a pointer to y which now points nowhere because y has been deleted from the memory.
                  //This is why you should be careful when capturing by reference.

    return 0;
}

Você também pode capturar variáveis ​​específicas especificando seus nomes. Apenas especificar seu nome os capturará por valor, especificando seu nome com um &antes os capturará por referência. Por exemplo, [=, &foo]capturará todas as variáveis ​​por valor, exceto as fooque serão capturadas por referência, e [&, foo]capturará todas as variáveis ​​por referência, exceto as fooque serão capturadas por valor. Você também pode capturar apenas variáveis ​​específicas, por exemplo [&foo], capturará foopor referência e não capturará outras variáveis. Você também pode capturar nenhuma variável usando []. Se você tentar usar uma variável em um lambda que não capturou, ela não será compilada. Aqui está um exemplo:

#include <functional>

int main(){
    int x = 4, y = 5;

    std::function<void(int)> myLambda = [y](int z){
        int xSquare = x * x;    //Compiler error because x wasn't captured
        int ySquare = y * y;    //OK because y was captured
        int zSquare = z * z;    //OK because z is an argument of the lambda
    };

    return 0;
}

Você não pode alterar o valor de uma variável que foi capturada por valor dentro de uma lambda (variáveis ​​capturadas por valor têm um consttipo dentro da lambda). Para fazer isso, você precisa capturar a variável por referência. Aqui está um exemplo:

#include <functional>

int main(){
    int x = 3, y = 5;
    std::function<void()> myLambda = [x, &y](){
        x = 2;    //Compiler error because x is captured by value and so it's of type const int inside the lambda
        y = 2;    //OK because y is captured by reference
    };
    x = 2;    //This is of course OK because we're not inside the lambda
    return 0;
}

Além disso, chamar lambdas não inicializadas é um comportamento indefinido e geralmente causa falha no programa. Por exemplo, nunca faça isso:

std::function<void()> lambda;
lambda();    //Undefined behavior because lambda is uninitialized

Exemplos

Aqui está o código para o que você queria fazer na sua pergunta usando lambdas:

#include <functional>    //Don't forget this, otherwise you won't be able to use the std::function type

int main(){
    std::function<void()> a = [](){
        // code
    }
    a();
    return 0;
}

Aqui está um exemplo mais avançado de um lambda:

#include <functional>    //For std::function
#include <iostream>      //For std::cout

int main(){
    int x = 4;
    std::function<float(int)> divideByX = [x](int y){
        return (float)y / (float)x;    //x is a captured variable, y is an argument
    }
    std::cout << divideByX(3) << std::endl;    //Prints 0.75
    return 0;
}

7

Não, não é permitido. Nem C nem C ++ suportam esse recurso por padrão, no entanto, TonyK aponta (nos comentários) que existem extensões para o compilador GNU C que permitem esse comportamento em C.


2
É suportado pelo compilador GNU C, como uma extensão especial. Mas apenas para C, não para C ++.
TonyK

Ah Não tenho extensões especiais no meu compilador C. Isso é bom saber, no entanto. Vou adicionar esse titbit à minha resposta.
Thomas Owens

Eu usei a extensão gcc para suporte a funções aninhadas (em C, porém, não em C ++). Funções aninhadas são uma coisa bacana (como em Pascal e Ada) para gerenciar estruturas complexas, porém coesas, que não devem ser de uso geral. Enquanto um usa o conjunto de ferramentas gcc, é a certeza de ser principalmente portátil para todas as arquiteturas orientadas. Porém, se houver a necessidade de compilar o código resultante com um compilador que não seja o gcc, é melhor evitar essas extensões e ficar o mais próximo possível do mantra ansi / posix.
Luis.espinal

7

Todos esses truques parecem (mais ou menos) funções locais, mas não funcionam assim. Em uma função local, você pode usar variáveis ​​locais de suas super funções. É meio que semi-globais. Muitos desses truques podem fazer isso. O mais próximo é o truque lambda do c ++ 0x, mas seu fechamento é limitado no tempo de definição, não no tempo de uso.


Agora acho que essa é a melhor resposta. Embora seja possível declarar uma função dentro de uma função (que eu uso o tempo todo), ela não é uma função local, conforme definida em muitos outros idiomas. Ainda é bom saber da possibilidade.
Alexis Wilke


4

Deixe-me postar uma solução aqui para C ++ 03 que considero a mais limpa possível. *

#define DECLARE_LAMBDA(NAME, RETURN_TYPE, FUNCTION) \
    struct { RETURN_TYPE operator () FUNCTION } NAME;

...

int main(){
  DECLARE_LAMBDA(demoLambda, void, (){ cout<<"I'm a lambda!"<<endl; });
  demoLambda();

  DECLARE_LAMBDA(plus, int, (int i, int j){
    return i+j;
  });
  cout << "plus(1,2)=" << plus(1,2) << endl;
  return 0;
}

(*) no mundo C ++, usando macros nunca é considerado limpo.


Alexis, você tem razão em dizer que não está perfeitamente limpo. Ainda está perto de ser limpo, pois expressa bem o que o programador pretendia fazer, sem efeitos colaterais. Considero que a arte da programação é escrever expressivamente legível por humanos que parece um romance.
Barney

2

Mas podemos declarar uma função dentro de main ():

int main()
{
    void a();
}

Embora a sintaxe esteja correta, às vezes pode levar à "Análise mais irritante":

#include <iostream>


struct U
{
    U() : val(0) {}
    U(int val) : val(val) {}

    int val;
};

struct V
{
    V(U a, U b)
    {
        std::cout << "V(" << a.val << ", " << b.val << ");\n";
    }
    ~V()
    {
        std::cout << "~V();\n";
    }
};

int main()
{
    int five = 5;
    V v(U(five), U());
}

=> sem saída do programa.

(Somente clang aviso após a compilação).

A análise mais irritante de C ++ novamente

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.