2009-05-18 12 views
70

Hice algo así por error en C++, y funciona. ¿Por qué puedo hacer esto?¿Por qué puedo definir estructuras y clases dentro de una función en C++?

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

    MyStruct s; 
    s.somevalue = 5; 
} 

Ahora después de hacer esto, que tipo de recuerdo haber leído sobre esto en otro truco, hace mucho tiempo, como una especie de herramienta de programación funcional del hombre pobre para C++, pero no puedo recordar por qué esto es válido o donde lo leí

¡Las respuestas a cualquier pregunta son bienvenidas!

Nota: Aunque al escribir la pregunta no obtuve ninguna referencia al this question, la barra lateral actual lo señala así que lo pondré aquí para referencia, de cualquier manera la pregunta es diferente pero podría ser útil.

Respuesta

8

Bueno, básicamente, ¿por qué no? Un struct en C (que se remonta al comienzo de los tiempos) era solo una forma de declarar una estructura de registro. Si quieres uno, ¿por qué no poder declararlo donde declararías una variable simple?

Una vez que hagas eso, recuerda que un objetivo de C++ era ser compatible con C si fuera posible. Así que se quedó.

+0

tipo de una característica interesante que ha sobrevivido, sino como j_random_hacker acaba de señalar que no es tan útil como lo estaba imaginando en C++:/ –

+0

Sí, las reglas de alcance eran raros en C también. Creo que ahora que tengo más de 25 años de experiencia con C++, quizás esforzarme por ser tan parecido a C como ellos lo hicieron, podría haber sido un error. Por otro lado, idiomas más elegantes como Eiffel no fueron adoptados con la misma facilidad. –

+0

Sí, he migrado una base de código C existente a C++ (pero no a Eiffel). – ChrisW

62

[EDIT 18/4/2013]: Afortunadamente, la restricción mencionada a continuación se ha eliminado en C++ 11, por lo que las clases definidas localmente son útiles después de todo! Gracias a commenter bamboon.

La capacidad de definir clases localmente habría que la creación de funtores personalizados (clases con un operator()(), por ejemplo, funciones de comparación para pasar a std::sort() o "cuerpos de bucle" para ser utilizado con std::for_each()) mucho más conveniente.

Desafortunadamente, C++ prohíbe el uso de clases definidas localmente con plantillas, ya que no tienen ningún vínculo. Debido a que la mayoría de las aplicaciones de funtores involucran tipos de plantillas que son plantillas en el tipo de functor, las clases definidas localmente no se pueden usar para esto; debe definirlas fuera de la función. :(

[EDIT 1/11/2009]

La cita relevante de la norma es:

14.3.1/2: .A tipo local, un tipo con ningún enlace, un tipo sin nombre o un tipo compuesto de cualquiera de estos tipos no se debe utilizar como un argumento de plantilla para un parámetro de tipo de plantilla.

+0

que explica por qué no pude hacer que funcione ahora. –

+2

Aunque empíricamente, esto parece funcionar con MSVC++ 8. (Pero no con g ++.) –

+0

podría ser un efecto secundario del soporte lambda, simplemente supongo que ... –

25

Una aplicación de definición local clases de C++ está en 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; 
} 

Aunque se puede hacer lo mismo con el espacio de nombres en el anonimato.

+0

¡Interesante! Aunque se aplicarán las restricciones relativas a las plantillas que menciono, este enfoque garantiza que las instancias de Impl no puedan crearse (¡ni siquiera mencionarse!) Excepto por CreateBase(). Así que esto parece una excelente manera de reducir la medida en que los clientes dependen de los detalles de implementación. +1. –

+16

Esa es una buena idea, no estoy seguro si la usaré pronto, pero probablemente sea una buena idea para sacarla en el bar para impresionar a algunas chicas :) –

+0

Te daré todos mis puntos SO si eso funciona. .. – markh44

2

Es para hacer arreglos de objetos que se han inicializado correctamente.

Tengo una clase C que no tiene un constructor predeterminado. Quiero un conjunto de objetos de la clase C. I averiguar cómo quiero esos objetos inicializan, entonces derivar una clase D de C con un método estático que proporciona el argumento para la C en D's constructor por defecto:

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

En aras de la simplicidad, este ejemplo utiliza un constructor trivial no predeterminado y un caso en el que los valores se conocen en tiempo de compilación. Es fácil extender esta técnica a los casos en los que desee una matriz de objetos inicializados con valores conocidos solo en tiempo de ejecución.

+0

¡Sin duda una aplicación interesante! Sin embargo, no estoy seguro si es sabio o incluso seguro: si necesita tratar esa matriz de D como una matriz de C (por ejemplo, necesita pasarla a una función tomando un parámetro 'D *') entonces esto se romperá silenciosamente si D es En realidad es más grande que C. (Creo que ...) –

+0

+ j_random_hacker, sizeof (D) == sizeof (C). Agregué un informe de sizeof() para ti. –

6

De hecho, es muy útil para hacer un trabajo de seguridad de excepciones basado en la pila. O limpieza general de una función con múltiples puntos de retorno. Esto a menudo se denomina modismo RAII (adquisición de recursos es initialzation).

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. 
} 
+2

'Limpiador más limpio();' Creo que será una declaración de función en lugar de una definición de objeto. – user

+1

@user Tiene razón. Para llamar al constructor predeterminado, debe escribir 'Cleaner cleaner;' o 'Cleaner cleaner {};'. – callyalater

Cuestiones relacionadas