Dlaczego mogę definiować struktury i klasy w ramach funkcji w C ++?

90

Po prostu przez pomyłkę zrobiłem coś takiego w C ++ i to działa. Dlaczego mogę to zrobić?

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

    MyStruct s;
    s.somevalue = 5;
}

Po zrobieniu tego trochę przypomniałem sobie, że czytałem o tej sztuczce gdzieś, dawno temu, jako rodzaj funkcjonalnego narzędzia programistycznego dla biednych dla C ++, ale nie pamiętam, dlaczego to jest ważne, ani gdzie to przeczytałem.

Odpowiedzi na którekolwiek z pytań są mile widziane!

Uwaga: Chociaż pisząc pytanie, nie otrzymałem żadnych odniesień do tego pytania , obecny pasek boczny wskazuje na to, więc umieszczę je tutaj jako odniesienie, tak czy inaczej pytanie jest inne, ale może być przydatne.

Robert Gould
źródło

Odpowiedzi:

70

[EDYCJA 18.04.2013]: Na szczęście ograniczenie wymienione poniżej zostało zniesione w C ++ 11, więc lokalnie zdefiniowane klasy są w końcu przydatne! Podziękowania dla komentatora Bamboon.

Możliwość definiowania klas lokalnie byłoby zrobić tworzenia niestandardowych funktory (zajęcia z operator()()np porównawczych dla funkcji przechodząc do std::sort()lub „Pętla” organów do stosowania z std::for_each()) znacznie wygodniejsze.

Niestety, C ++ zabrania używania lokalnie zdefiniowanych klas z szablonami , ponieważ nie mają one żadnego powiązania. Ponieważ większość aplikacji funktorów obejmuje typy szablonów, które są szablonami na typie funktora, nie można do tego użyć klas zdefiniowanych lokalnie - należy je zdefiniować poza funkcją. :(

[EDYCJA 11.01.2009]

Odpowiedni cytat z normy to:

14.3.1 / 2: .Typ lokalny, typ bez powiązań, typ nienazwany lub typ złożony z któregokolwiek z tych typów nie może być używany jako argument szablonu dla parametru typu szablonu.

j_random_hacker
źródło
2
Chociaż empirycznie wydaje się, że działa to z MSVC ++ 8. (Ale nie z g ++.)
j_random_hacker
Używam gcc 4.3.3 i wydaje się, że tam działa: pastebin.com/f65b876b . Czy masz odniesienie do miejsca, w którym norma tego zabrania? Wydaje mi się, że można go łatwo utworzyć w momencie użycia.
Catskul
@Catskul: 14.3.1 / 2: "Typ lokalny, typ bez powiązań, typ nienazwany lub typ złożony z któregokolwiek z tych typów nie może być używany jako argument szablonu dla parametru typu szablonu". Wydaje mi się, że uzasadnieniem jest to, że lokalne klasy wymagałyby jeszcze jednej porcji informacji, aby wepchnąć je w zniekształcone nazwy, ale nie wiem tego na pewno. Oczywiście konkretny kompilator może oferować rozszerzenia, aby obejść ten problem, ponieważ wydaje się, że MSVC ++ 8 i najnowsze wersje g ++ tak.
j_random_hacker
9
To ograniczenie zostało zniesione w C ++ 11.
Stephan Dollberg
31

Jedno zastosowanie lokalnie zdefiniowanych klas C ++ znajduje się we wzorcu projektowym Factory :


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

Chociaż możesz zrobić to samo z anonimową przestrzenią nazw.

Nikolai Fetissov
źródło
Ciekawy! Chociaż ograniczenia dotyczące szablonów, o których wspomniałem, będą miały zastosowanie, to podejście gwarantuje, że instancje Impl nie mogą być tworzone (ani nawet o nich mówiono!), Chyba że za pomocą CreateBase (). Wydaje się więc, że jest to doskonały sposób na zmniejszenie stopnia, w jakim klienci zależą od szczegółów implementacji. +1.
j_random_hacker
26
To fajny pomysł, nie jestem pewien, czy będę go używać w najbliższym czasie, ale prawdopodobnie dobry do wyciągnięcia przy barze, aby zaimponować niektórym pisklętom :)
Robert Gould
2
(Przy okazji mówię o pisklętach, nie o odpowiedzi!)
markh44
9
lol Robert ... Tak, nic tak nie imponuje kobiecie jak wiedza o niejasnych zakamarkach C ++ ...
j_random_hacker
10

W rzeczywistości jest to bardzo przydatne do wykonywania pewnych prac związanych z bezpieczeństwem wyjątków opartych na stosie. Lub ogólne czyszczenie funkcji z wieloma punktami powrotu. Jest to często nazywane idiomem RAII (pozyskiwanie zasobów to inicjalizacja).

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.
}
simong
źródło
5
Cleaner cleaner();Myślę, że będzie to raczej deklaracja funkcji niż definicja obiektu.
użytkownik
2
@user Masz rację. Aby wywołać domyślny konstruktor, powinien napisać Cleaner cleaner;lub Cleaner cleaner{};.
callyalater
Klasy wewnątrz funkcji nie mają nic wspólnego z RAII, a poza tym nie jest to poprawny kod w C ++ i nie będzie się kompilował.
Michaił Wasiljew
1
Nawet wewnątrz funkcji, klasy takie jak ta są DOKŁADNIE tym, o co chodzi w RAII w C ++.
Christopher Bruns
9

Cóż, w zasadzie, czemu nie? A structw C (cofając się do zarania czasu) było tylko sposobem na zadeklarowanie struktury rekordu. Jeśli chcesz, dlaczego nie móc zadeklarować go w miejscu, w którym zadeklarowałbyś prostą zmienną?

Kiedy już to zrobisz, pamiętaj, że celem C ++ było być kompatybilne z C, jeśli to w ogóle możliwe. Tak zostało.

Charlie Martin
źródło
coś w rodzaju fajnej funkcji, która przetrwała, ale jak właśnie zauważył j_random_hacker, nie jest tak przydatna, jak sobie wyobrażałem w C ++: /
Robert Gould
Tak, zasady dotyczące zakresu też były dziwne w C. Myślę, że teraz, gdy mam ponad 25 lat doświadczenia z C ++, być może dążenie do tego, by być tak samo podobnym do C, jak oni, mogło być błędem. Z drugiej strony, bardziej eleganckie języki, takie jak Eiffel, nie zostały przyjęte prawie tak łatwo.
Charlie Martin
Tak, przeprowadziłem migrację istniejącej bazy kodu C do C ++ (ale nie do Eiffla).
ChrisW
3

Służy do tworzenia tablic obiektów, które są odpowiednio zainicjalizowane.

Mam klasę C, która nie ma domyślnego konstruktora. Chcę mieć tablicę obiektów klasy C.Wymyślam, jak chcę, aby te obiekty były inicjalizowane, a następnie wyprowadzam klasę D z C za pomocą metody statycznej, która dostarcza argument dla domyślnego konstruktora C w 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;
}

Ze względu na prostotę w tym przykładzie zastosowano trywialny konstruktor niedomyślny i przypadek, w którym wartości są znane w czasie kompilacji. Technikę tę można w prosty sposób rozszerzyć na przypadki, w których chcesz, aby tablica obiektów została zainicjowana wartościami, które są znane tylko w czasie wykonywania.

Thomas L Holaday
źródło
Z pewnością ciekawa aplikacja! Nie jestem pewien, czy jest mądry, czy nawet bezpieczny - jeśli chcesz traktować tę tablicę D jako tablicę C (np. Musisz przekazać ją do funkcji pobierającej D*parametr), to po cichu się zepsuje, jeśli D jest faktycznie większy niż C . (Myślę ...)
j_random_hacker
+ j_random_hacker, sizeof (D) == sizeof (C). Dodałem dla Ciebie raport sizeof ().
Thomas L Holaday