Possiamo avere funzioni all'interno di funzioni in C ++?


225

Intendo qualcosa come:

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

  return 0;
}

1
Perché stai cercando di farlo? Spiegare il tuo scopo potrebbe consentire a qualcuno di dirti il ​​modo giusto per raggiungere il tuo obiettivo.
Thomas Owens,

3
gcc supporta le funzioni nidificate come estensione non standard. Ma meglio non usarlo anche se stai usando gcc. E in modalità C ++, non è disponibile comunque.
Sven Marnach,

27
@Thomas: Perché sarebbe bene ridurre la portata di un? Le funzioni nelle funzioni sono normali in altre lingue.
Johan Kotlinski,

64
Sta parlando di funzioni nidificate. Analogamente alla capacità delle classi successive all'interno delle classi, desidera nidificare una funzione all'interno di una funzione. In realtà, ho avuto anche situazioni in cui l'avrei fatto, se fosse stato possibile. Esistono lingue (ad es. F #) che lo consentono e posso dirti che può rendere il codice molto più chiaro, leggibile e gestibile senza inquinare una libreria con dozzine di funzioni di helpers che sono inutili al di fuori di un contesto molto specifico. ;)
Mephane

16
@Thomas - Le funzioni nidificate possono essere un meccanismo eccellente per interrompere funzioni / algoritmi complessi senza senza riempire l'ambito corrente con funzioni che non sono di uso generale nell'ambito compreso. Pascal e Ada hanno un ottimo supporto (IMO) per loro. Lo stesso con Scala e molte altre lingue vecchie / nuove rispettate. Come qualsiasi altra funzione, possono anche essere abusati, ma questa è una funzione dello sviluppatore. IMO, sono stati molto più vantaggiosi che dannosi.
luis.espinal,

Risposte:


271

C ++ moderno - Sì con lambdas!

Nelle versioni correnti di c ++ (C ++ 11, C ++ 14 e C ++ 17), è possibile avere funzioni all'interno di funzioni sotto forma di 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 può anche modificare le variabili locali tramite ** cattura per riferimento *. Con l'acquisizione per riferimento, lambda ha accesso a tutte le variabili locali dichiarate nell'ambito di lambda. Può modificarli e cambiarli 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 - Non direttamente, ma sì con funzioni statiche all'interno delle classi locali

C ++ non lo supporta direttamente.

Detto questo, puoi avere classi locali e possono avere funzioni (non statico static), quindi puoi farlo in qualche misura, anche se è un po 'un kludge:

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

  X::a();

  return 0;
}

Tuttavia, metterei in discussione la prassi. Tutti sanno (bene, ora che lo fai, comunque :)) C ++ non supporta le funzioni locali, quindi sono abituati a non averle. Non sono tuttavia abituati a quel kludge. Vorrei dedicare un po 'di tempo a questo codice per assicurarmi che sia davvero lì solo per consentire le funzioni locali. Non bene.


3
Main richiede anche due argomenti se vuoi essere pedante riguardo al tipo di ritorno. :) (O è facoltativo ma non il ritorno in questi giorni? Non riesco a tenere il passo.)
Leo Davidson,

3
Questo è semplicemente negativo: infrange ogni convenzione di codice buono e pulito. Non riesco a pensare a un singolo caso in cui questa è una buona idea.
Thomas Owens,

19
@Thomas Owens: va bene se hai bisogno di una funzione di callback e non vuoi inquinare qualche altro spazio dei nomi con esso.
Leo Davidson,

9
@Leo: lo standard dice che ci sono due forme consentite per main: int main()eint main(int argc, char* argv[])
John Dibling,

8
Lo standard dice int main()e int main(int argc, char* argv[])deve essere supportato e altri possono essere supportati ma tutti hanno un int di ritorno.
JoeG

260

A tutti gli effetti, C ++ supporta questo tramite lambdas : 1

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

Qui, fc'è un oggetto lambda che funge da funzione locale in main. È possibile specificare le acquisizioni per consentire alla funzione di accedere agli oggetti locali.

Dietro le quinte, fc'è un oggetto funzione (cioè un oggetto di un tipo che fornisce un operator()). Il tipo di oggetto funzione viene creato dal compilatore basato su lambda.


1 dal C ++ 11


5
Ah, è pulito! Non ci ho pensato. Questo è molto meglio della mia idea, +1da parte mia.
sabato

1
@sbi: In realtà ho usato strutture locali per simulare questo in passato (sì, mi vergogno adeguatamente di me stesso). Ma l'utilità è limitata dal fatto che le strutture locali non creano una chiusura, cioè non è possibile accedere alle variabili locali in esse. Devi passarli e archiviarli esplicitamente tramite un costruttore.
Konrad Rudolph,

1
@Konrad: un altro problema è che in C ++ 98 non è necessario utilizzare i tipi locali come parametri del modello. Penso che C ++ 1x abbia rimosso questa limitazione. (O era quel C ++ 03?)
sbi

3
@luis: devo essere d'accordo con Fred. Stai attribuendo un significato a lambda che semplicemente non hanno (né in C ++ né in altre lingue con cui ho lavorato - che non includono Python e Ada, per la cronaca). Inoltre, fare questa distinzione non è significativo in C ++ perché C ++ non ha funzioni locali, punto. Ha solo lambdas. Se vuoi limitare l'ambito di una cosa simile a una funzione a una funzione, le tue uniche scelte sono lambda o la struttura locale menzionata in altre risposte. Direi che quest'ultimo è un po 'troppo contorto per essere di alcun interesse pratico.
Konrad Rudolph,

2
@AustinWBryan No, le lambda in C ++ sono solo zucchero sintattico per i funzioni e non hanno spese generali. C'è una domanda con maggiori dettagli da qualche parte su questo sito.
Konrad Rudolph,

42

Le classi locali sono già state menzionate, ma qui è un modo per farle apparire ancora di più come funzioni locali, usando un sovraccarico operatore () e una classe anonima:

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

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

Non consiglio di usare questo, è solo un trucco divertente (può fare, ma non dovrei).


Aggiornamento 2014:

Con l'ascesa di C ++ 11 qualche tempo fa, ora puoi avere funzioni locali la cui sintassi ricorda un po 'JavaScript:

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

1
Dovrebbe essere operator () (unsigned int val), ti manca un set di parentesi.
Joe D,

1
In realtà, questa è una cosa perfettamente ragionevole da fare se è necessario passare questo funzione a una funzione o algoritmo stl, come std::sort(), o std::for_each().
Dima,

1
@Dima: Sfortunatamente, in C ++ 03, i tipi definiti localmente non possono essere usati come argomenti di template. C ++ 0x risolve questo problema, ma fornisce anche le soluzioni molto migliori di lambda, quindi non lo faresti ancora.
Ben Voigt,

Oops, hai ragione. Colpa mia. Tuttavia, questo non è solo un trucco divertente. Sarebbe stato utile se fosse stato permesso. :)
Dima,

3
La ricorsione è supportata. Tuttavia, non è possibile utilizzare autoper dichiarare la variabile. Stroustrup fornisce l'esempio: function<void(char*b, char*e)> rev=[](char*b, char*e) { if( 1<e-b ) { swap( *b, *--e); rev(++b,e); } };per invertire una stringa dati i puntatori di inizio e fine.
Eponimo

17

No.

Cosa stai cercando di fare?

soluzione alternativa:

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

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

}

2
Si noti che l'approccio di istanza di classe viene fornito con un'allocazione di memoria ed è quindi dominato dall'approccio statico.
ManuelSchneid3r

14

A partire da C ++ 11 è possibile utilizzare lambda appropriate . Vedi le altre risposte per maggiori dettagli.


Vecchia risposta: puoi, in un certo senso, ma devi imbrogliare e usare una classe fittizia:

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

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

Non sono sicuro, tranne creando invece un oggetto (che aggiunge altrettanto rumore, IMO). A meno che non ci sia qualcosa di intelligente che puoi fare con gli spazi dei nomi, ma non riesco a pensarci e probabilmente non è una buona idea abusare della lingua più di quello che siamo già. :)
Leo Davidson,

Il liberarsi del manichino :: è in una delle altre risposte.
Sebastian Mach,

8

Come altri hanno già detto, puoi usare le funzioni nidificate usando le estensioni del linguaggio gnu in gcc. Se tu (o il tuo progetto) segui la toolchain di gcc, il tuo codice sarà per lo più portatile attraverso le diverse architetture targetizzate dal compilatore gcc.

Tuttavia, se esiste un possibile requisito che potrebbe essere necessario compilare il codice con una toolchain diversa, allora starei lontano da tali estensioni.


Avrei anche camminato con cura quando usavo le funzioni annidate. Sono una bella soluzione per gestire la struttura di blocchi di codice complessi ma coerenti (i cui pezzi non sono pensati per uso esterno / generale.) Sono anche molto utili nel controllo dell'inquinamento dello spazio dei nomi (una preoccupazione molto reale con naturalmente complessi / lunghe lezioni in lingue verbose).

Ma come qualsiasi cosa, possono essere aperti agli abusi.

È triste che C / C ++ non supporti tali funzionalità come standard. La maggior parte delle varianti pasquali e Ada fanno (quasi tutte le lingue basate su Algol lo fanno). Lo stesso con JavaScript. Lo stesso vale per le lingue moderne come la Scala. Lo stesso con linguaggi venerabili come Erlang, Lisp o Python.

E proprio come con C / C ++, purtroppo Java (con il quale guadagno la maggior parte della mia vita) non lo fa.

Cito Java qui perché vedo diversi poster che suggeriscono l'uso di classi e metodi di classe come alternative alle funzioni nidificate. E questa è anche la soluzione alternativa tipica in Java.

Risposta breve: No.

In questo modo si tende a introdurre una complessità artificiale e inutile in una gerarchia di classi. A parità di condizioni, l'ideale è disporre di una gerarchia di classi (e dei relativi spazi dei nomi e ambiti) che rappresenti un dominio effettivo il più semplice possibile.

Le funzioni nidificate aiutano a gestire la complessità "privata" e all'interno delle funzioni. In mancanza di tali strutture, si dovrebbe cercare di evitare di propagare quella complessità "privata" fuori e nel proprio modello di classe.

Nel software (e in qualsiasi disciplina di ingegneria), la modellazione è una questione di compromessi. Pertanto, nella vita reale, ci saranno giustificate eccezioni a tali regole (o meglio alle linee guida). Procedere con cura, però.


8

Non puoi avere funzioni locali in C ++. Tuttavia, C ++ 11 ha lambdas . Le lambda sono sostanzialmente variabili che funzionano come funzioni.

Una lambda ha il tipo std::function(in realtà non è del tutto vero , ma nella maggior parte dei casi puoi supporre che lo sia). Per utilizzare questo tipo, è necessario #include <functional>. std::functionè un modello, prendendo come argomento modello il tipo restituito e i tipi di argomento, con la sintassi std::function<ReturnType(ArgumentTypes). Ad esempio, std::function<int(std::string, float)>è una lambda che restituisce un inte accetta due argomenti, uno std::stringe uno float. Il più comune è std::function<void()>, che non restituisce nulla e non accetta argomenti.

Una volta dichiarata una lambda, viene chiamata proprio come una normale funzione, usando la sintassi lambda(arguments).

Per definire un lambda, usa la sintassi [captures](arguments){code}(ci sono altri modi per farlo, ma non li menzionerò qui). argumentsè quali argomenti prende lambda ed codeè il codice che dovrebbe essere eseguito quando viene chiamato lambda. Di solito metti [=]o [&]come catture. [=]significa che acquisisci tutte le variabili nell'ambito in cui il valore è definito dal valore, il che significa che manterranno il valore che avevano quando è stata dichiarata la lambda. [&]significa che si acquisiscono tutte le variabili nell'ambito per riferimento, il che significa che avranno sempre il loro valore corrente, ma se vengono cancellate dalla memoria il programma andrà in crash. Ecco alcuni esempi:

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

Puoi anche acquisire variabili specifiche specificandone i nomi. Basta specificare il loro nome per catturarli per valore, specificando il loro nome con un &prima li cattureranno per riferimento. Ad esempio, [=, &foo]acquisirà tutte le variabili in base al valore tranne quelle fooche verranno acquisite per riferimento e [&, foo]acquisirà tutte le variabili in base al riferimento tranne quelle fooche verranno acquisite in base al valore. È anche possibile acquisire solo variabili specifiche, ad esempio [&foo]acquisirà fooper riferimento e non acquisirà altre variabili. Puoi anche non catturare alcuna variabile usando []. Se si tenta di utilizzare una variabile in una lambda che non è stata acquisita, non verrà compilata. Ecco un esempio:

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

Non è possibile modificare il valore di una variabile catturata dal valore all'interno di un lambda (le variabili catturate dal valore hanno un consttipo all'interno del lambda). Per fare ciò, è necessario acquisire la variabile per riferimento. Ecco un esempio:

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

Inoltre, chiamare lambda non inizializzati è un comportamento indefinito e di solito causa l'arresto anomalo del programma. Ad esempio, non farlo mai:

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

Esempi

Ecco il codice per quello che volevi fare nella tua domanda 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;
}

Ecco un esempio più avanzato di 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

No, non è permesso. Né C né C ++ supportano questa funzionalità per impostazione predefinita, tuttavia TonyK sottolinea (nei commenti) che esistono estensioni del compilatore C GNU che abilitano questo comportamento in C.


2
È supportato dal compilatore GNU C, come un'estensione speciale. Ma solo per C, non C ++.
TonyK,

Ah. Non ho estensioni speciali nel mio compilatore C. Buono a sapersi, però. Aggiungerò quel titbit alla mia risposta.
Thomas Owens,

Ho usato l'estensione gcc per il supporto di funzioni nidificate (in C, tuttavia, non in C ++). Le funzioni nidificate sono una cosa intelligente (come in Pascal e Ada) per la gestione di strutture complesse, ma coerenti, che non sono pensate per essere di uso generale. Finché si utilizza la toolchain gcc, si garantisce che è per lo più portatile per tutte le architetture target. Ma se c'è un cambiamento nel dover compilare il codice risultante con un compilatore non gcc, allora è meglio evitare tali estensioni e rimanere il più vicino possibile al mantra ansi / posix.
luis.espinal,

7

Tutti questi trucchi sembrano (più o meno) funzioni locali, ma non funzionano così. In una funzione locale puoi usare le variabili locali delle sue super funzioni. È una specie di semi-globali. Nessuno di questi trucchi può farlo. Il più vicino è il trucco lambda di c ++ 0x, ma la sua chiusura è limitata nel tempo di definizione, non nel tempo di utilizzo.


Ora penso che questa sia la risposta migliore. Sebbene sia possibile dichiarare una funzione all'interno di una funzione (che uso sempre), non è una funzione locale come definita in molte altre lingue. È ancora bello sapere della possibilità.
Alexis Wilke,

6

Non è possibile definire una funzione libera all'interno di un'altra in C ++.


1
Non con ansi / posix, ma puoi farlo con le estensioni gnu.
luis.espinal,

4

Consentitemi di pubblicare una soluzione qui per C ++ 03 che considero il più pulito possibile. *

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

(*) nel mondo C ++ l'uso di macro non è mai considerato pulito.


Alexis, hai ragione a dire che non è perfettamente pulito. È ancora vicino alla pulizia perché esprime bene ciò che il programmatore intendeva fare, senza effetti collaterali. Ritengo che l'arte della programmazione stia scrivendo in modo leggibile espressivo e che si legge come un romanzo.
Barney,

2

Ma possiamo dichiarare una funzione dentro main ():

int main()
{
    void a();
}

Sebbene la sintassi sia corretta, a volte può portare all'analisi più fastidiosa:

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

=> nessun output del programma.

(Solo avvertimento Clang dopo la compilazione).

L'analisi più fastidiosa di C ++ di nuovo

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.