Pouvons-nous avoir des fonctions à l'intérieur des fonctions en C ++?


225

Je veux dire quelque chose comme:

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

  return 0;
}

1
Pourquoi essayez-vous de faire ça? Expliquer votre objectif pourrait permettre à quelqu'un de vous indiquer la bonne façon d'atteindre votre objectif.
Thomas Owens du

3
gcc prend en charge les fonctions imbriquées en tant qu'extension non standard. Mais mieux vaut ne pas l'utiliser même si vous utilisez gcc. Et en mode C ++, il n'est pas disponible de toute façon.
Sven Marnach du

27
@Thomas: Parce qu'il serait bon de réduire la portée d'un? Les fonctions dans les fonctions sont une caractéristique habituelle dans d'autres langues.
Johan Kotlinski

64
Il parle de fonctions imbriquées. De la même manière que pour pouvoir passer aux classes suivantes à l'intérieur des classes, il veut imbriquer une fonction dans une fonction. En fait, j'ai eu des situations où je l'aurais fait aussi, si c'était possible. Il existe des langages (par exemple F #) qui permettent cela, et je peux vous dire que cela peut rendre le code beaucoup plus clair, lisible et maintenable sans polluer une bibliothèque avec des dizaines de fonctions d'aide inutiles en dehors d'un contexte très spécifique. ;)
Mephane

16
@Thomas - fonctions imbriquées peuvent être un excellent mécanisme pour briser les fonctions / algorithmes complexes sans sans remplir la portée actuelle des fonctions qui ne sont pas d'usage général dans le cadre englobante. Pascal et Ada ont (IMO) un soutien adorable pour eux. Même chose avec Scala et de nombreuses autres langues respectées anciennes / nouvelles. Comme toute autre fonctionnalité, ils peuvent également être utilisés abusivement, mais c'est une fonction du développeur. OMI, ils ont été beaucoup plus bénéfiques que préjudiciables.
luis.espinal

Réponses:


271

C ++ moderne - Oui avec des lambdas!

Dans les versions actuelles de c ++ (C ++ 11, C ++ 14 et C ++ 17), vous pouvez avoir des fonctions à l'intérieur de fonctions sous la forme d'un 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 peut également modifier des variables locales via ** capture-by-reference *. Avec capture par référence, le lambda a accès à toutes les variables locales déclarées dans la portée du lambda. Il peut les modifier et les changer normalement.

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 et C ++ 03 - Pas directement, mais oui avec des fonctions statiques à l'intérieur des classes locales

C ++ ne prend pas cela directement en charge.

Cela dit, vous pouvez avoir des classes locales et elles peuvent avoir des fonctions (non staticou static), vous pouvez donc obtenir cela dans une certaine mesure, bien que ce soit un peu compliqué:

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

  X::a();

  return 0;
}

Cependant, je remettrais en question la pratique. Tout le monde sait (enfin, maintenant que vous le faites, de toute façon :)) C ++ ne prend pas en charge les fonctions locales, ils sont donc habitués à ne pas les avoir. Ils ne sont cependant pas habitués à cette complaisance. Je passerais pas mal de temps sur ce code pour m'assurer qu'il n'est vraiment là que pour autoriser les fonctions locales. Pas bon.


3
Main prend également deux arguments si vous voulez être pédant sur le type de retour. :) (Ou est-ce facultatif mais pas le retour ces jours-ci? Je ne peux pas suivre.)
Leo Davidson

3
C'est tout simplement mauvais - cela casse toutes les conventions de bon code propre. Je ne peux pas penser à un seul cas où c'est une bonne idée.
Thomas Owens

19
@Thomas Owens: C'est bien si vous avez besoin d'une fonction de rappel et que vous ne voulez pas polluer un autre espace de noms avec elle.
Leo Davidson

9
@Leo: La norme indique qu'il existe deux formes autorisées pour le principal: int main()etint main(int argc, char* argv[])
John Dibling

8
La norme dit int main()et int main(int argc, char* argv[])doit être prise en charge et d'autres peuvent être prises en charge, mais elles ont toutes return int.
JoeG

260

À toutes fins utiles, C ++ prend en charge cela via lambdas : 1

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

Voici fun objet lambda qui agit comme une fonction locale dansmain . Des captures peuvent être spécifiées pour permettre à la fonction d'accéder aux objets locaux.

Dans les coulisses, se ftrouve un objet fonction (c'est-à-dire un objet d'un type qui fournit un operator()). Le type d'objet fonction est créé par le compilateur sur la base du lambda.


1 depuis C ++ 11


5
Ah, c'est bien! Je n'y ai pas pensé. C'est bien mieux que mon idée, +1de ma part.
sbi

1
@sbi: J'ai déjà utilisé des structures locales pour simuler cela dans le passé (oui, j'ai honte de moi-même). Mais l'utilité est limitée par le fait que les structures locales ne créent pas de fermeture, c'est-à-dire que vous ne pouvez pas y accéder aux variables locales. Vous devez les transmettre et les stocker explicitement via un constructeur.
Konrad Rudolph

1
@Konrad: Un autre problème avec eux est qu'en C ++ 98, vous ne devez pas utiliser de types locaux comme paramètres de modèle. Je pense que C ++ 1x a levé cette restriction. (Ou était-ce C ++ 03?)
sbi

3
@luis: Je dois être d'accord avec Fred. Vous attachez un sens aux lambdas qu'ils n'ont tout simplement pas (ni en C ++ ni dans d'autres langages avec lesquels j'ai travaillé - qui n'incluent pas Python et Ada, pour mémoire). De plus, faire cette distinction n'est tout simplement pas significatif en C ++ car C ++ n'a pas de fonctions locales, point. Il n'a que des lambdas. Si vous voulez limiter la portée d'une chose semblable à une fonction à une fonction, vos seuls choix sont des lambdas ou la structure locale mentionnée dans d'autres réponses. Je dirais que ce dernier est un peu trop alambiqué pour avoir un intérêt pratique.
Konrad Rudolph

2
@AustinWBryan Non, les lambdas en C ++ ne sont que du sucre syntaxique pour les foncteurs et n'ont aucun frais généraux. Il y a une question plus détaillée quelque part sur ce site.
Konrad Rudolph

42

Les classes locales ont déjà été mentionnées, mais voici un moyen de les faire 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 d'utiliser cela, c'est juste une astuce amusante (peut le faire, mais à mon humble avis 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 le JavaScript:

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

1
Devrait être operator () (unsigned int val), il vous manque un jeu de parenthèses.
Joe D

1
En fait, c'est une chose parfaitement raisonnable à faire si vous devez passer ce foncteur à une fonction ou un algorithme stl, comme std::sort()ou std::for_each().
Dima

1
@Dima: Malheureusement, en C ++ 03, les types définis localement ne peuvent pas être utilisés comme arguments de modèle. C ++ 0x corrige cela, mais fournit également des solutions beaucoup plus agréables de lambdas, donc vous ne le feriez toujours pas.
Ben Voigt

Oups, vous avez raison. Ma faute. Mais encore, ce n'est pas seulement un tour drôle. Cela aurait été utile si cela avait été autorisé. :)
Dima

3
La récursivité est prise en charge. Cependant, vous ne pouvez pas utiliser autopour déclarer la variable. Stroustrup donne l'exemple: function<void(char*b, char*e)> rev=[](char*b, char*e) { if( 1<e-b ) { swap( *b, *--e); rev(++b,e); } };pour inverser une chaîne avec des pointeurs de début et de fin donnés.
Éponyme du

17

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()

}

2
Notez que l'approche d'instanciation de classe est livrée avec une allocation de mémoire et est donc dominée par l'approche statique.
ManuelSchneid3r

14

À partir de C ++ 11, vous pouvez utiliser des lambdas appropriés . Voir les autres réponses pour plus de détails.


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

Pas sûr que vous puissiez, sauf en créant un objet à la place (ce qui ajoute autant de bruit, IMO). À moins qu'il y ait quelque chose d'intelligent que vous puissiez faire avec les espaces de noms, mais je n'y pense pas et ce n'est probablement pas une bonne idée d'abuser de la langue plus que ce que nous sommes déjà. :)
Leo Davidson

Le se débarrasser du mannequin :: est dans l'une des autres réponses.
Sebastian Mach

8

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) vous en tenez à 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 possible que vous deviez compiler du code avec une chaîne d'outils différente, je resterais à l'écart de ces extensions.


Je ferais également preuve de prudence lors de l'utilisation de fonctions imbriquées. Ils sont une belle solution pour gérer la structure de blocs de code complexes, mais cohérents (dont les morceaux ne sont pas destinés à un usage externe / général.) Ils sont également très utiles pour contrôler la pollution de l'espace de noms (une préoccupation très réelle avec naturellement complexe / longues classes dans des langues verbeuses.)

Mais comme tout, ils peuvent être ouverts à des abus.

Il est regrettable que C / C ++ ne prenne pas en charge ces fonctionnalités en tant que norme. La plupart des variantes pascal et Ada font (presque toutes les langues basées sur Algol font). Même chose avec JavaScript. Même chose avec les langues modernes comme Scala. Idem avec les langages vénérables comme Erlang, Lisp ou Python.

Et tout comme avec C / C ++, malheureusement, Java (avec lequel je gagne la majeure partie de 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 de 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 englobants) représentant un domaine réel aussi simple que possible.

Les fonctions imbriquées aident à gérer la complexité «privée» au sein de la fonction. Faute de ces installations, on devrait essayer d'éviter de propager cette complexité «privée» à l'extérieur et dans le modèle de classe.

Dans le logiciel (et dans toute discipline d'ingénierie), la modélisation est une question de compromis. Ainsi, dans la vie réelle, il y aura des exceptions justifiées à ces règles (ou plutôt directives). Procédez avec prudence, cependant.


8

Vous ne pouvez pas avoir de fonctions locales en C ++. Cependant, C ++ 11 a des lambdas . Les lambdas 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::functionest 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)>un lambda renvoie un intet prend deux arguments, un std::stringet unfloat . Le plus courant est celui 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, en utilisant la syntaxe lambda(arguments) .

Pour définir un lambda, utilisez la syntaxe [captures](arguments){code}(il existe d'autres façons de le faire, mais je ne les mentionnerai pas ici). argumentsest le type d'arguments pris par le lambda, et codec'est le code qui doit être exécuté lorsque le lambda est appelé. Habituellement, vous mettez [=]ou en [&]tant que captures. [=]signifie que vous capturez toutes les variables dans la portée dans laquelle la valeur est définie par valeur, ce qui signifie qu'elles conserveront la valeur qu'elles avaient lorsque la lambda a été déclarée. [&]signifie que vous capturez toutes les variables dans 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. La simple spécification de leur nom les capturera par valeur, la spécification de leur nom avec un &avant les capturera par référence. Par exemple, [=, &foo]capturera toutes les variables par valeur, sauf celles fooqui seront capturées par référence, et [&, foo]capturera toutes les variables par référence, sauf celles fooqui seront capturées par valeur. Vous pouvez également capturer uniquement des variables spécifiques, par exemple [&foo]capturer foopar référence et ne capturer aucune autre variable. Vous pouvez également capturer aucune variable du tout 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 modifier la valeur d'une variable qui a été capturée par la valeur à l'intérieur d'un lambda (les variables capturées par la valeur ont un consttype à l'intérieur du 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és est un comportement non défini et entraînera généralement le plantage du programme. Par exemple, ne faites jamais ceci:

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

Exemples

Voici le code de ce que vous vouliez faire dans votre question avec 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é d'un 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

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


2
Il est pris en charge par le compilateur GNU C, comme une extension spéciale. Mais seulement pour C, pas C ++.
TonyK

Ah. Je n'ai pas d'extensions spéciales dans mon compilateur C. C'est bon à savoir, cependant. Je vais ajouter ce morceau à ma réponse.
Thomas Owens du

J'ai utilisé l'extension gcc pour la prise en charge des fonctions imbriquées (en C, cependant, pas en C ++). Les fonctions imbriquées sont une chose astucieuse (comme dans Pascal et Ada) pour gérer des structures complexes mais cohérentes qui ne sont pas censées être d'utilisation générale. Tant que l'on utilise la chaîne d'outils gcc, il est assuré d'être principalement portable pour toutes les architectures ciblées. Mais s'il y a un changement dans la nécessité de compiler le code résultant avec un compilateur non-gcc, alors, il est préférable d'éviter de telles extensions et de rester aussi proche que possible du mantra ansi / posix.
luis.espinal

7

Toutes ces astuces ressemblent (plus ou moins) à des fonctions locales, mais elles ne fonctionnent pas comme ça. Dans une fonction locale, vous pouvez utiliser des variables locales de ses super fonctions. C'est une sorte de 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.


Maintenant, je pense que c'est la meilleure réponse. Bien qu'il soit possible de déclarer une fonction dans une fonction (que j'utilise tout le temps), ce n'est pas une fonction locale telle que définie dans de nombreux autres langages. Il est toujours bon de connaître la possibilité.
Alexis Wilke

6

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


1
Pas avec ansi / posix, mais vous pouvez avec des extensions gnu.
luis.espinal

4

Permettez-moi de poster ici une solution pour C ++ 03 que je considère 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 ++ utilisant des macros n'est jamais considéré comme propre.


Alexis, tu as raison de dire qu'elle n'est pas parfaitement propre. Il est encore proche d'être propre car il exprime bien ce que le programmeur voulait faire, sans effets secondaires. Je considère que l'art de la programmation consiste à écrire de manière lisible et expressive, qui se lit comme un roman.
Barney

2

Mais nous pouvons déclarer une fonction à l'intérieur de main ():

int main()
{
    void a();
}

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

#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.

(Seul avertissement Clang après la compilation).

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.