[C++] Adelante declarando una enumeración en c ++


Answers

La declaración directa de enumeraciones también es posible en C ++ 0x. Anteriormente, la razón por la que los tipos enum no podían ser declarados hacia adelante se debe a que el tamaño de la enumeración depende de su contenido. Siempre que la aplicación especifique el tamaño de la enumeración, se puede anunciar con anticipación:

enum Enum1;                   //Illegal in C++ and C++0x; no size is explicitly specified.
enum Enum2 : unsigned int;    //Legal in C++0x.
enum class Enum3;             //Legal in C++0x, because enum class declarations have a default type of "int".
enum class Enum4: unsigned int; //Legal C++0x.
enum Enum2 : unsigned short;  //Illegal in C++0x, because Enum2 was previously declared with a different type.
Question

Estoy tratando de hacer algo como lo siguiente:

enum E;

void Foo(E e);

enum E {A, B, C};

que el compilador rechaza He echado un vistazo rápido a Google y el consenso parece ser "no puedes hacerlo", pero no puedo entender por qué. ¿Alguien puede explicar? Muchas gracias.

Aclaración 2: estoy haciendo esto porque tengo métodos privados en una clase que toma dicha enumeración, y no quiero que se expongan los valores de la enumeración, así que, por ejemplo, no quiero que nadie sepa que E se define como

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

ya que el proyecto X no es algo que deseo que mis usuarios conozcan.

Por lo tanto, quería reenviar declarar la enumeración para poder poner los métodos privados en el archivo de encabezado, declarar la enumeración internamente en la cpp y distribuir el archivo y el encabezado de la biblioteca incorporada a las personas.

En cuanto al compilador, es GCC.




Para VC, esta es la prueba sobre la declaración directa y la especificación del tipo subyacente:

  1. el siguiente código está compilado ok.
    typedef int myint;
    enum T ;
    void foo(T * tp )
    {
        * tp = (T)0x12345678;
    }
    enum T : char
    {
        A
    };

Pero recibí la advertencia para / W4 (/ W3 no incurrir en esta advertencia)

advertencia C4480: extensión no estándar utilizada: especificando el tipo subyacente para enum 'T'

  1. VC (Microsoft (R) C / C ++ Optimizing Compiler Version 15.00.30729.01 para 80x86) parece defectuoso en el caso anterior:

    • al ver enum T; VC supone que el tipo de entrada T utiliza el valor predeterminado de 4 bytes int como tipo subyacente, por lo que el código de ensamblado generado es:
    ?foo@@YAXPAW4T@@@Z PROC                 ; foo
    ; File e:\work\c_cpp\cpp_snippet.cpp
    ; Line 13
        push    ebp
        mov ebp, esp
    ; Line 14
        mov eax, DWORD PTR _tp$[ebp]
        mov DWORD PTR [eax], 305419896      ; 12345678H
    ; Line 15
        pop ebp
        ret 0
    ?foo@@YAXPAW4T@@@Z ENDP                 ; foo

El código ensamblador anterior se extrae de /Fatest.asm directamente, no es mi suposición personal. ¿Ves el mov DWORD PTR [eax], 305419896; 12345678H línea?

el siguiente fragmento de código lo demuestra:

    int main(int argc, char *argv)
    {
        union {
            char ca[4];
            T t;
        }a;
        a.ca[0] = a.ca[1] = a.[ca[2] = a.ca[3] = 1;
        foo( &a.t) ;
        printf("%#x, %#x, %#x, %#x\n",  a.ca[0], a.ca[1], a.ca[2], a.ca[3] );
        return 0;
    }

el resultado es: 0x78, 0x56, 0x34, 0x12

  • después de eliminar la declaración directa de enum T y mover la definición de función foo después de la definición de enum T: el resultado es correcto:

la instrucción de tecla anterior se convierte en:

mov BYTE PTR [eax], 120; 00000078H

el resultado final es: 0x78, 0x1, 0x1, 0x1

Tenga en cuenta que el valor no se sobrescribe

Por lo tanto, el uso de la declaración directa de enum en VC se considera perjudicial.

Por cierto, para no sorprender, la sintaxis para la declaración del tipo subyacente es la misma que en C #. En la práctica, encontré que vale la pena guardar 3 bytes especificando el tipo subyacente como char cuando hablo con el sistema integrado, que tiene memoria limitada.




Usted define una enumeración para restringir los posibles valores de los elementos del tipo a un conjunto limitado. Esta restricción se aplicará en tiempo de compilación.

Cuando adelante declarar el hecho de que usará un 'conjunto limitado' más adelante no agrega ningún valor: el código posterior necesita conocer los valores posibles para beneficiarse de él.

Aunque al compilador le preocupa el tamaño del tipo enumerado, la intención de la enumeración se pierde al reenviarla.




Mi solución a su problema sería:

1 - use int en lugar de enums: declare sus datos en un espacio de nombre anónimo en su archivo CPP (no en el encabezado):

namespace
{
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
}

Como tus métodos son privados, nadie se meterá con los datos. Incluso podría ir más allá para probar si alguien le envía datos no válidos:

namespace
{
   const int FUNCTIONALITY_begin = 0 ;
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
   const int FUNCTIONALITY_end = 3 ;

   bool isFunctionalityCorrect(int i)
   {
      return (i >= FUNCTIONALITY_begin) && (i < FUNCTIONALITY_end) ;
   }
}

2: crea una clase completa con instancias de const limitadas, como en Java. Reenviar declare la clase, y luego defínalo en el archivo CPP, e instaure solo los valores tipo enum. Hice algo así en C ++, y el resultado no fue tan satisfactorio como se esperaba, ya que necesitaba un código para simular una enumeración (construcción de copias, operator =, etc.).

3: Como se propuso anteriormente, use la enumeración declarada en privado. A pesar de que un usuario verá su definición completa, no podrá usarla ni usar los métodos privados. Por lo tanto, normalmente podrá modificar la enumeración y el contenido de los métodos existentes sin necesidad de volver a compilar el código con su clase.

Creo que sería la solución 3 o 1.




Como la enumeración puede ser un tamaño integral de tamaño variable (el compilador decide qué tamaño tiene una enumeración dada), el puntero a la enumeración también puede tener un tamaño variable, ya que es un tipo integral (los caracteres tienen punteros de un tamaño diferente en algunas plataformas por ejemplo).

Por lo tanto, el compilador ni siquiera puede permitirle avanzar: declare el enum y el usuario un puntero a él, porque incluso allí, necesita el tamaño de la enumeración.




Forward declarar cosas en C ++ es muy útil porque acelera drásticamente el tiempo de compilación . Puede reenviar declarar varias cosas en C ++, incluidas: struct , class , function , etc.

¿Pero puedes reenviar declarar una enum en C ++?

No, no puedes.

Pero, ¿por qué no permitirlo? Si estuviera permitido, podría definir su tipo de enum en su archivo de encabezado y sus valores enum en su archivo fuente. Parece que debería estar permitido ¿no?

Incorrecto.

En C ++ no hay un tipo predeterminado para enum como hay en C # (int). En C ++, el compilador determinará que su tipo de enum será del tipo que corresponda al rango de valores que tiene para su enum .

Qué significa eso?

Significa que el tipo subyacente de tu enum no puede determinarse completamente hasta que hayas definido todos los valores de la enum . Lo que mans no puede separar la declaración y la definición de su enum . Y, por lo tanto, no puede reenviar declarar una enum en C ++.

El estándar ISO C ++ S7.2.5:

El tipo subyacente de una enumeración es un tipo integral que puede representar todos los valores del enumerador definidos en la enumeración. Está definido por implementación qué tipo integral se utiliza como el tipo subyacente para una enumeración, excepto que el tipo subyacente no debe ser mayor que int menos que el valor de un enumerador no pueda caber en una int o unsigned int . Si la enumeradora-lista está vacía, el tipo subyacente es como si la enumeración tuviera un único enumerador con valor 0. El valor de sizeof() aplicado a un tipo de enumeración, un objeto de tipo enumeración o un enumerador, es el valor de sizeof() aplicado al tipo subyacente.

Puede determinar el tamaño de un tipo enumerado en C ++ utilizando el operador sizeof . El tamaño del tipo enumerado es el tamaño de su tipo subyacente. De esta forma, puede adivinar qué tipo está utilizando su compilador para su enum .

¿Qué pasa si especifica el tipo de su enum explícitamente así:

enum Color : char { Red=0, Green=1, Blue=2};
assert(sizeof Color == 1);

¿Puedes entonces reenviar tu enum ?

No. Pero, ¿por qué no?

Especificar el tipo de una enum no es realmente parte del estándar actual de C ++. Es una extensión de VC ++. Sin embargo, será parte de C ++ 0x.

Source




Si realmente no desea que su enumeración aparezca en su archivo de encabezado Y asegúrese de que solo se use por métodos privados, entonces una solución puede ser seguir el principio pimpl.

Es una técnica que asegura ocultar las partes internas de la clase en los encabezados simplemente declarando:

class A 
{
public:
    ...
private:
    void* pImpl;
};

Luego, en su archivo de implementación (cpp), declara una clase que será la representación de las partes internas.

class AImpl
{
public:
    AImpl(A* pThis): m_pThis(pThis) {}

    ... all private methods here ...
private:
    A* m_pThis;
};

Debe crear dinámicamente la implementación en el constructor de clase y eliminarla en el destructor y al implementar el método público, debe usar:

((AImpl*)pImpl)->PrivateMethod();

Hay ventajas para usar pimpl, una es que desacopla el encabezado de su implementación, no es necesario volver a compilar otras clases cuando se cambia la implementación de una clase. Otra es que acelera tu tiempo de compilación porque tus encabezados son muy simples.

Pero es un dolor de usar, así que realmente debería preguntarse si tan solo declarar su enumeración como privado en el encabezado es un problema.




De hecho, no hay tal cosa como una declaración directa de enum. Como la definición de una enumeración no contiene ningún código que pueda depender de otro código que use la enumeración, generalmente no es un problema definir la enumeración por completo cuando la declara por primera vez.

Si el uso exclusivo de su enumeración es por funciones de miembro privadas, puede implementar la encapsulación teniendo la enumeración como un miembro privado de esa clase. La enumeración todavía tiene que estar completamente definida en el punto de declaración, es decir, dentro de la definición de clase. Sin embargo, este no es un problema mayor, ya que declarar funciones de miembro privado allí, y no es una exposición peor de los elementos internos de implementación que eso.

Si necesita un grado más profundo de ocultación para los detalles de su implementación, puede dividirlo en una interfaz abstracta, que solo consiste en funciones virtuales puras, y una clase concreta y completamente oculta que implementa (hereda) la interfaz. La creación de instancias de clase puede ser manejada por una fábrica o una función de miembro estático de la interfaz. De esa manera, incluso el nombre de clase real, y mucho menos sus funciones privadas, no se expondrá.




Solo señalando que la razón en realidad es que el tamaño de la enumeración aún no se conoce después de la declaración directa. Bueno, usas la declaración forward de una estructura para poder pasar un puntero o referirte a un objeto desde un lugar al que se hace referencia también en la definición de struct declarada forward.

Forward declarar una enumeración no sería demasiado útil, porque uno desearía poder pasar el enum por valor. Ni siquiera podrías tener un puntero, porque recientemente me dijeron que algunas plataformas usan punteros de diferentes tamaños para char que para int o long. Entonces todo depende del contenido de la enumeración.

El estándar actual de C ++ explícitamente no permite hacer algo como

enum X;

(en 7.1.5.3/1 ). Pero el próximo estándar de C ++ para el próximo año permite lo siguiente, lo que me convenció de que el problema realmente tiene que ver con el tipo subyacente:

enum X : int;

Se lo conoce como una declaración enum "opaca". Incluso puede usar X por valor en el siguiente código. Y sus enumeradores se pueden definir más adelante en una redeclaración posterior de la enumeración. Ver 7.2 en el borrador de trabajo actual.




Links