Perché posso definire strutture e classi all'interno di una funzione in C ++?


90

Ho solo erroneamente fatto qualcosa di simile in C ++ e funziona. Perché posso farlo?

int main(int argc, char** argv) {
    struct MyStruct
    {
      int somevalue;
    };

    MyStruct s;
    s.somevalue = 5;
}

Ora, dopo averlo fatto, mi sono ricordato di aver letto di questo trucco da qualche parte, molto tempo fa, come una sorta di strumento di programmazione funzionale per i poveri per C ++, ma non riesco a ricordare perché sia ​​valido, o dove l'ho letto.

Le risposte a entrambe le domande sono benvenute!

Nota: anche se durante la scrittura della domanda non ho ricevuto alcun riferimento a questa domanda , la barra laterale corrente lo indica, quindi lo inserirò qui come riferimento, in entrambi i casi la domanda è diversa ma potrebbe essere utile.


Risposte:


70

[EDIT 18/4/2013]: Fortunatamente, la restrizione menzionata di seguito è stata rimossa in C ++ 11, quindi le classi definite localmente sono utili dopo tutto! Grazie al commentatore Bamboon.

La capacità di definire le classi a livello locale sarebbe rendere la creazione funtori personalizzati (classi con un operator()(), ad esempio funzioni di confronto per il passaggio alla std::sort()o "corpi loop" da utilizzare con std::for_each()) molto più conveniente.

Sfortunatamente, C ++ vieta l'uso di classi definite localmente con modelli , poiché non hanno collegamenti. Poiché la maggior parte delle applicazioni dei funtori coinvolge tipi di modello che sono modellati sul tipo funtore, le classi definite localmente non possono essere utilizzate per questo: è necessario definirle al di fuori della funzione. :(

[EDIT 1/11/2009]

La citazione pertinente dallo standard è:

14.3.1 / 2: Un tipo locale, un tipo senza collegamento, un tipo senza nome o un tipo composto da uno qualsiasi di questi tipi non deve essere utilizzato come argomento-modello per un parametro-tipo di modello.


2
Sebbene empiricamente, questo sembra funzionare con MSVC ++ 8. (Ma non con g ++.)
j_random_hacker

Sto usando gcc 4.3.3 e sembra funzionare lì: pastebin.com/f65b876b . Hai un riferimento a dove lo standard lo proibisce? Mi sembra che potrebbe essere facilmente istanziato al momento dell'uso.
Catskul

@Catskul: 14.3.1 / 2: "Un tipo locale, un tipo senza collegamento, un tipo senza nome o un tipo composto da uno qualsiasi di questi tipi non deve essere utilizzato come argomento-modello per un parametro-tipo di modello". Immagino che la logica sia che le classi locali richiederebbero ancora un altro mucchio di informazioni per essere inserite in nomi alterati, ma non lo so per certo. Ovviamente un particolare compilatore può offrire estensioni per aggirare questo problema, come sembra che MSVC ++ 8 e le versioni recenti di g ++ facciano.
j_random_hacker

9
Questa restrizione è stata rimossa in C ++ 11.
Stephan Dollberg

31

Un'applicazione delle classi C ++ definite localmente è in Factory design pattern :


// In some header
class Base
{
public:
    virtual ~Base() {}
    virtual void DoStuff() = 0;
};

Base* CreateBase( const Param& );

// in some .cpp file
Base* CreateBase( const Params& p )
{
    struct Impl: Base
    {
        virtual void DoStuff() { ... }
    };

    ...
    return new Impl;
}

Anche se puoi fare lo stesso con lo spazio dei nomi anonimo.


Interessante! Sebbene si applicheranno le restrizioni relative ai modelli che ho citato, questo approccio garantisce che le istanze di Impl non possano essere create (o neppure parlate!) Se non da CreateBase (). Quindi questo sembra un ottimo modo per ridurre la misura in cui i client dipendono dai dettagli di implementazione. +1.
j_random_hacker

26
È un'idea chiara, non sono sicuro che la userò presto, ma probabilmente è una buona idea da tirare fuori al bar per impressionare alcune ragazze :)
Robert Gould

2
(Sto parlando delle ragazze BTW, non della risposta!)
markh44

9
lol Robert ... Sì, niente impressiona una donna come conoscere angoli oscuri di C ++ ...
j_random_hacker

10

In realtà è molto utile per fare un po 'di lavoro sulla sicurezza delle eccezioni basato sullo stack. O pulizia generale da una funzione con più punti di ritorno. Questo è spesso chiamato idioma RAII (acquisizione di risorse è inizializzazione).

void function()
{

    struct Cleaner
    {
        Cleaner()
        {
            // do some initialization code in here
            // maybe start some transaction, or acquire a mutex or something
        }

        ~Cleaner()
        {
             // do the associated cleanup
             // (commit your transaction, release your mutex, etc.)
        }
    };

    Cleaner cleaner;

    // Now do something really dangerous
    // But you know that even in the case of an uncaught exception, 
    // ~Cleaner will be called.

    // Or alternatively, write some ill-advised code with multiple return points here.
    // No matter where you return from the function ~Cleaner will be called.
}

5
Cleaner cleaner();Penso che questa sarà una dichiarazione di funzione piuttosto che una definizione di oggetto.
utente

2
@user Hai ragione. Per chiamare il costruttore predefinito, dovrebbe scrivere Cleaner cleaner;o Cleaner cleaner{};.
callyalater

Le classi all'interno delle funzioni non hanno nulla a che fare con RAII e, inoltre, questo non è un codice C ++ valido e non verrà compilato.
Mikhail Vasilyev

1
Anche all'interno delle funzioni, classi come questa sono PRECISAMENTE ciò di cui parla RAII in C ++.
Christopher Bruns

9

In pratica, perché no? A structin C (che risale all'alba dei tempi) era solo un modo per dichiarare una struttura di record. Se ne vuoi uno, perché non essere in grado di dichiararlo dove dichiareresti una semplice variabile?

Una volta fatto, ricorda che l'obiettivo di C ++ era quello di essere compatibile con C, se possibile. Così è rimasto.


una sorta di bella caratteristica che è sopravvissuta, ma come j_random_hacker ha appena sottolineato, non è così utile come immaginavo in C ++: /
Robert Gould

Sì, anche le regole di scoping erano strane in C. Penso che, ora che ho più di 25 anni di esperienza con C ++, che forse sforzarsi di essere tanto simili a C quanto loro potrebbe essere stato un errore. D'altra parte, linguaggi più eleganti come Eiffel non sono stati adottati altrettanto facilmente.
Charlie Martin

Sì, ho migrato una base di codice C esistente in C ++ (ma non in Eiffel).
ChrisW


3

Serve per creare array di oggetti inizializzati correttamente.

Ho una classe C che non ha un costruttore predefinito. Voglio un array di oggetti di classe C. Capisco come voglio inizializzare quegli oggetti, quindi ricavo una classe D da C con un metodo statico che fornisce l'argomento per il C nel costruttore predefinito di D:

#include <iostream>
using namespace std;

class C {
public:
  C(int x) : mData(x)  {}
  int method() { return mData; }
  // ...
private:
  int mData;
};

void f() {

  // Here I am in f.  I need an array of 50 C objects starting with C(22)

  class D : public C {
  public:
    D() : C(D::clicker()) {}
  private:
    // I want my C objects to be initialized with consecutive
    // integers, starting at 22.
    static int clicker() { 
      static int current = 22;
      return current++;
    } 
  };

  D array[50] ;

  // Now I will display the object in position 11 to verify it got initialized
  // with the right value.  

  cout << "This should be 33: --> " << array[11].method() << endl;

  cout << "sizodf(C): " << sizeof(C) << endl;
  cout << "sizeof(D): " << sizeof(D) << endl;

  return;

}

int main(int, char **) {
  f();
  return 0;
}

Per semplicità, questo esempio utilizza un banale costruttore non predefinito e un caso in cui i valori sono noti in fase di compilazione. È semplice estendere questa tecnica ai casi in cui si desidera un array di oggetti inizializzato con valori noti solo in fase di esecuzione.


Sicuramente un'applicazione interessante! Non sono sicuro che sia saggio o addirittura sicuro: se devi trattare quell'array di D come un array di C (ad esempio, devi passarlo a una funzione che prende un D*parametro), questo si interromperà silenziosamente se D è effettivamente più grande di C . (Penso ...)
j_random_hacker

+ j_hacker_random, sizeof (D) == sizeof (C). Ho aggiunto un rapporto sizeof () per te.
Thomas L Holaday
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.