web-dev-qa-db-fra.com

Peut-on avoir des fonctions à l'intérieur des fonctions?

Je veux dire quelque chose comme:

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

  return 0;
}
162
Rella

Non, C++ ne supporte pas ça. 

Modifier: Cette réponse est ancienne. Pendant ce temps, C++ 11 a des lambda qui peuvent donner un résultat similaire - voir les réponses ci-dessous.

Cela dit, vous pouvez avoir des classes locales et des fonctions (non -static ou static), afin que vous puissiez obtenir ceci dans une certaine mesure, bien que ce soit un peu un problème: 

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

  X::a();

  return 0;
}

Cependant, je remets en question la pratique. Tout le monde le sait (enfin, maintenant :)), C++ ne supporte pas les fonctions locales, donc ils sont habitués à ne pas en avoir. Cependant, ils ne sont pas habitués à ce kludge. Je passerais pas mal de temps sur ce code pour m'assurer qu'il ne sert vraiment qu'à permettre les fonctions locales. Pas bon. 

168
sbi

À toutes fins utiles, C++ le supporte via lambdas :1

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

Ici, f est un objet lambda qui agit comme une fonction locale dans main. Des captures peuvent être spécifiées pour permettre à la fonction d'accéder aux objets locaux.

Dans les coulisses, f est un objet function (c'est-à-dire un objet d'un type fournissant une operator()). Le type d'objet de fonction est créé par le compilateur basé sur le lambda.


1 depuis C++ 11

236
Konrad Rudolph

Les classes locales ont déjà été mentionnées, mais voici un moyen de les laisser apparaître encore plus comme des fonctions locales, en utilisant une surcharge operator () et une classe anonyme:

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

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

Je ne conseille pas sur l'utilisation de ceci, c'est juste une astuce amusante (peut faire, mais imho ne devrait pas).


Mise à jour 2014:

Avec la montée en puissance de C++ 11 il y a quelque temps, vous pouvez maintenant avoir des fonctions locales dont la syntaxe rappelle un peu JavaScript:

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

Non.

Qu'essayez-vous de faire? 

solution de contournement:

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

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

}
14
Nim

Ancienne réponse: Vous pouvez, en quelque sorte, mais vous devez tricher et utiliser une classe factice:

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

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

Réponse plus récente: Les versions plus récentes de C++ permettent également à lambdas de le faire mieux/correctement. Voir les réponses plus haut sur la page.

10
Leo Davidson

Non, ce n'est pas permis. Ni C, ni C++ ne supportent cette fonctionnalité par défaut, cependant TonyK souligne (dans les commentaires) qu'il existe des extensions du compilateur GNU C qui permettent ce comportement en C.

7
Thomas Owens

Comme d'autres l'ont mentionné, vous pouvez utiliser des fonctions imbriquées en utilisant les extensions de langage gnu dans gcc. Si vous (ou votre projet) respectez la chaîne d’outils gcc, votre code sera principalement portable sur les différentes architectures ciblées par le compilateur gcc.

Cependant, s'il est éventuellement nécessaire de compiler du code avec une chaîne d'outils différente, je resterais à l'écart de telles extensions.


Je serais également prudent avec l'utilisation de fonctions imbriquées. Ils constituent une belle solution pour gérer la structure de blocs de code complexes, mais cohérents (dont les éléments ne sont pas destinés à un usage général/externe). Ils sont également très utiles pour contrôler la pollution de l’espace de nommage longues classes dans les langues parlées.)

Mais comme n'importe quoi, ils peuvent être exposés à des abus. 

Il est regrettable que C/C++ ne prenne pas en charge de telles fonctionnalités en tant que norme. La plupart des variantes de Pascal et Ada (presque tous les langages basés sur ALGOL). Même avec JavaScript. Même chose avec les langues modernes comme Scala. Même chose avec des langages vénérables comme Erlang, LISP ou Python. 

Et comme pour le C/C++, malheureusement, Java (avec lequel je gagne le plus possible ma vie) ne le fait pas.

Je mentionne Java ici parce que je vois plusieurs affiches suggérant l’utilisation de classes et de méthodes de classe comme alternatives aux fonctions imbriquées. Et c'est aussi la solution de contournement typique en Java.

Réponse courte: Non.

Cela tend à introduire une complexité artificielle et inutile dans une hiérarchie de classes. Toutes choses étant égales par ailleurs, l’idéal est d’avoir une hiérarchie de classes (et ses espaces de noms et portées englobantes) représentant un domaine réel aussi simple que possible.

Les fonctions imbriquées aident à gérer la complexité "privée" au sein d'une fonction. En l'absence de ces installations, il convient d'éviter de propager cette complexité "privée" dans un modèle de classe.

Dans les logiciels (et dans toutes les disciplines de l’ingénierie), la modélisation est une question de compromis. Ainsi, dans la réalité, il y aura des exceptions justifiées à ces règles (ou plutôt à des directives). Procédez avec prudence, cependant.

6
luis.espinal

Vous ne pouvez pas définir une fonction libre dans une autre en C++.

6
Prasoon Saurav

Vous ne pouvez pas avoir de fonctions locales en C++. Cependant, C++ 11 a lambdas . Les Lambda sont essentiellement des variables qui fonctionnent comme des fonctions.

Un lambda a le type std::function ( en fait, ce n'est pas tout à fait vrai , mais dans la plupart des cas, vous pouvez le supposer). Pour utiliser ce type, vous devez #include <functional>. std::function est un modèle, prenant comme argument de modèle le type de retour et les types d'argument, avec la syntaxe std::function<ReturnType(ArgumentTypes). Par exemple, std::function<int(std::string, float)> est un lambda renvoyant une int et prenant deux arguments, un std::string et un float. Le plus commun est std::function<void()>, qui ne renvoie rien et ne prend aucun argument.

Une fois qu'un lambda est déclaré, il est appelé comme une fonction normale, à l'aide de la syntaxe lambda(arguments).

Pour définir un lambda, utilisez la syntaxe [captures](arguments){code} (il y a d'autres façons de le faire, mais je ne les mentionnerai pas ici). arguments est le type d'argument utilisé par lambda, et code est le code à exécuter lors de l'appel de lambda. Habituellement, vous mettez [=] ou [&] en tant que captures. [=] signifie que vous capturez toutes les variables de la portée dans lesquelles la valeur est définie par valeur, ce qui signifie qu'elles conserveront la valeur qu'elles avaient lors de la déclaration de lambda. [&] signifie que vous capturez toutes les variables de la portée par référence, ce qui signifie qu'elles auront toujours leur valeur actuelle, mais si elles sont effacées de la mémoire, le programme se bloquera. Voici quelques exemples:

#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;
}

Vous pouvez également capturer des variables spécifiques en spécifiant leurs noms. Le simple fait de spécifier leur nom les capturera par valeur, en spécifiant leur nom avec un & avant de les capturer par référence. Par exemple, [=, &foo] capturera toutes les variables par valeur sauf foo qui sera capturé par référence et [&, foo] capturera toutes les variables par référence sauf foo qui sera capturé par valeur. Vous pouvez également capturer uniquement des variables spécifiques. Par exemple, [&foo] capturera foo par référence et ne capturera aucune autre variable. Vous pouvez également ne capturer aucune variable en utilisant []. Si vous essayez d'utiliser une variable dans un lambda que vous n'avez pas capturée, elle ne sera pas compilée. Voici un exemple:

#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;
}

Vous ne pouvez pas changer la valeur d'une variable qui a été capturée par valeur dans un lambda (les variables capturées par une valeur ont un type const dans le lambda). Pour ce faire, vous devez capturer la variable par référence. Voici un exemple:

#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;
}

En outre, l'appel de lambdas non initialisé constitue un comportement indéfini et provoque généralement le blocage du programme. Par exemple, ne faites jamais ceci:

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

Exemples

Voici le code pour ce que vous vouliez faire dans votre question en utilisant 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;
}

Voici un exemple plus avancé de 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;
}
5
Donald Duck

Toutes ces astuces ressemblent (plus ou moins) à des fonctions locales, mais elles ne fonctionnent pas comme ça. Dans une fonction locale, vous pouvez utiliser les variables locales de ses super fonctions. C'est un peu des semi-globaux. Aucune de ces astuces ne peut le faire. Le plus proche est l'astuce lambda de c ++ 0x, mais sa fermeture est liée au temps de définition, pas au temps d'utilisation.

5
royas

Me laisser poster ici une solution pour C++ 03 que je considère comme la plus propre possible. *

#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;
}

(*) dans le monde C++, l'utilisation de macros n'est jamais considérée comme propre. 

3
Barnabas Szabolcs

Mais nous pouvons déclarer une fonction dans main ():

int main()
{
    void a();
}

Bien que la syntaxe soit correcte, elle peut parfois conduire à "l'analyse la plus frustrante":

#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());
}

=> pas de sortie de programme.

(Avertissement Clang seulement après compilation).

L'analyse la plus frustrante de C++ à nouveau

2
trig-ger

Lorsque vous essayez d'implémenter une fonction dans un autre corps de fonction, vous devez obtenir cette error en tant que définition illégale:

error C2601: 'a' : local function definitions are illegal
IntelliSense: expected a ';'

Alors n'essayez plus jamais.

0
Mohammadreza Panahi