it-swarm-es.tech

Adelante declarando una enumeración en C++

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 entiendo por qué. ¿Alguien puede explicar?

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, por lo que, por ejemplo, no quiero que nadie sepa que E se define como

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

como el proyecto X no es algo que quiero que mis usuarios conozcan.

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

En cuanto al compilador, es GCC.

247
szevvy

La razón por la que no se puede reenviar la enumeración es que, sin conocer los valores, el compilador no puede saber el almacenamiento requerido para la variable enum. Los compiladores de C++ pueden especificar el espacio de almacenamiento real en función del tamaño necesario para contener todos los valores especificados. Si todo lo que está visible es la declaración hacia adelante, la unidad de traducción no puede saber qué tamaño de almacenamiento se habrá elegido, podría ser un char o un int, o algo más.


De la Sección 7.2.5 de la Norma ISO C++:

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 la implementación qué tipo de integral se usa como tipo subyacente para una enumeración, excepto que el tipo subyacente no será mayor que int, a menos que el valor de un enumerador no pueda caber en int o unsigned int. Si el enumerator-list está vacío, el tipo subyacente es como si la enumeración tuviera un solo enumerador con el valor 0. El valor de sizeof() aplicado a un tipo de enumeración, un objeto de tipo de enumeración o un enumerador, es el valor de sizeof() aplicado al tipo subyacente.

Dado que el llamante a la función debe conocer los tamaños de los parámetros para configurar correctamente la pila de llamadas, el número de enumeraciones en una lista de enumeración debe ser conocido antes del prototipo de función.

Actualización: En C++ 0X se ha propuesto y aceptado una sintaxis para declarar de antemano los tipos de enumeración. Puede ver la propuesta en http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf

201
KJAWolf

La declaración de enums hacia adelante también es posible en C++ 0x. Anteriormente, la razón por la que los tipos de enumeración no podían ser declarados hacia adelante es porque 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 declarar hacia delante:

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.
184
user119017

Estoy agregando una respuesta actualizada aquí, dados los desarrollos recientes.

Puede reenviar y declarar una enumeración en C++ 11, siempre que declare su tipo de almacenamiento al mismo tiempo. La sintaxis se ve así:

enum E : short;
void foo(E e);

....

enum E : short
{
    VALUE_1,
    VALUE_2,
    ....
}

De hecho, si la función nunca se refiere a los valores de la enumeración, no necesita la declaración completa en ese momento.

Esto es compatible con G ++ 4.6 y posteriores (-std=c++0x o -std=c++11 en versiones más recientes). Visual C++ 2013 soporta esto; en versiones anteriores tiene algún tipo de soporte no estándar que aún no he descubierto - encontré alguna sugerencia de que una declaración directa simple es legal, pero YMMV.

69
Tom

Declarar cosas hacia adelante en C++ es muy útil porque acelera dramáticamente el tiempo de compilación . Puede reenviar declarar varias cosas en C++ incluyendo: struct, class, function, etc ...

¿Pero puedes reenviar una enum en C++?

No tu no puedes.

Pero ¿por qué no permitirlo? Si estuviera permitido, podría definir su tipo enum en su archivo de encabezado y sus valores enum en su archivo fuente. Suena como que se debe permitir ¿verdad?

Incorrecto.

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

Qué significa eso?

Significa que el tipo subyacente de su enum no se puede determinar completamente hasta que haya definido todos los valores de la enum. 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++.

La norma 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 la implementación qué tipo de integral se usa como tipo subyacente para una enumeración, excepto que el tipo subyacente no será mayor que int, a menos que el valor de un enumerador no pueda caber en un int o unsigned int. Si la lista de enumeradores está vacía, el tipo subyacente es como si la enumeración tuviera un solo enumerador con valor 0. El valor de sizeof() aplicado a un tipo de enumeración, un objeto de 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 manera, puedes adivinar qué tipo de compilador está usando para tu enum.

¿Qué sucede si especifica el tipo de su enum explícitamente de esta manera?

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 en realidad parte del estándar actual de C++. Es una extensión de VC++. Sin embargo, será parte de C++ 0x.

Fuente

30
Brian R. Bondy

[Mi respuesta es incorrecta, pero la he dejado aquí porque los comentarios son útiles].

La enumeración de declaración directa no es estándar, ya que no se garantiza que los punteros a diferentes tipos de enumeración tengan el mismo tamaño. Es posible que el compilador necesite ver la definición para saber qué punteros de tamaño se pueden usar con este tipo.

En la práctica, al menos en todos los compiladores populares, los punteros a enumeraciones son un tamaño consistente. La declaración de enums hacia adelante se proporciona como una extensión de lenguaje por Visual C++, por ejemplo.

13
James Hopkin

De hecho, no hay tal cosa como una declaración de enumeración hacia adelante. 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 completamente cuando la declara por primera vez.

Si el uso exclusivo de su enumeración es mediante funciones miembro privadas, puede implementar la encapsulación al tener la enumeración como miembro privado de esa clase. La enumeración aún tiene que estar completamente definida en el punto de la declaración, es decir, dentro de la definición de clase. Sin embargo, este no es un problema mayor al declarar que las funciones de los miembros privados están allí, y no es una peor exposición de los aspectos internos de la implementación.

Si necesita un mayor grado de ocultamiento para los detalles de su implementación, puede dividirla en una interfaz abstracta, que consiste solo en funciones virtuales puras y en una clase concreta, 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 miembro estática de la interfaz. De esa manera, incluso el nombre real de la clase, por no hablar de sus funciones privadas, no será expuesto.

7
Alexey Feldgendler

Simplemente notando 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 de reenvío. Bueno, utiliza la declaración hacia adelante de una estructura para poder pasar un puntero o referirse a un objeto desde un lugar al que se hace referencia en la definición de la estructura declarada hacia adelante también.

Reenviar la declaración de una enumeración no sería muy útil, porque uno desearía poder pasar el valor enum de la enumeración. Ni siquiera se podía tener un puntero hacia él, porque recientemente me dijeron que algunas plataformas usan punteros de diferente tamaño para char que para int o long. Así que todo depende del contenido de la enumeración.

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

enum X;

(en 7.1.5.3/1). Pero el siguiente estándar de C++ que se entregará 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 conoce como una declaración de enumeración "opaca". Incluso puede usar X por valor en el siguiente código. Y sus enumeradores se pueden definir posteriormente en una redeclaración posterior de la enumeración. Ver 7.2 en el borrador de trabajo actual.

5

Lo haría de esta manera:

[en el encabezado público]

typedef unsigned long E;

void Foo(E e);

[en el encabezado interno]

enum Econtent { FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X,
  FORCE_32BIT = 0xFFFFFFFF };

Al agregar FORCE_32BIT, nos aseguramos de que Econtent compile por mucho tiempo, por lo que es intercambiable con E.

4
Laurie Cheers

¡Parece que no puede ser declarado en GCC!

Discusión interesante aquí

2
prakash

Puede envolver la enumeración en una estructura, agregando algunos constructores y conversiones de tipo, y en su lugar, declare en adelante la estructura.

#define ENUM_CLASS(NAME, TYPE, VALUES...) \
struct NAME { \
    enum e { VALUES }; \
    explicit NAME(TYPE v) : val(v) {} \
    NAME(e v) : val(v) {} \
    operator e() const { return e(val); } \
    private:\
        TYPE val; \
}

Esto parece funcionar: http://ideone.com/TYtP2

2
Leszek Swirski

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 la del principio pimpl.

Es una técnica que garantiza 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 los elementos internos.

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 la 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 clase de su implementación, sin necesidad de volver a compilar otras clases al cambiar una implementación de clase. Otra es que acelera el tiempo de compilación porque los encabezados son muy simples.

Pero es una molestia utilizarlo, por lo que debería preguntarse si declarar su enumeración como privada en el encabezado es un problema.

2
Vincent Robert

En mis proyectos, adopté la - Namespace-Bound Enumeration technique para tratar con enums de componentes heredados y de terceros. Aquí hay un ejemplo:

adelante.h:

namespace type
{
    class legacy_type;
    typedef const legacy_type& type;
}

enum.h:

// May be defined here or pulled in via #include.
namespace legacy
{
    enum evil { x , y, z };
}


namespace type
{
    using legacy::evil;

    class legacy_type
    {
    public:
        legacy_type(evil e)
            : e_(e)
        {}

        operator evil() const
        {
            return e_;
        }

    private:
        evil e_;
    };
}

foo.h:

#include "forward.h"

class foo
{
public:
    void f(type::type t);
};

foo.cc:

#include "foo.h"

#include <iostream>
#include "enum.h"

void foo::f(type::type t)
{
    switch (t)
    {
        case legacy::x:
            std::cout << "x" << std::endl;
            break;
        case legacy::y:
            std::cout << "y" << std::endl;
            break;
        case legacy::z:
            std::cout << "z" << std::endl;
            break;
        default:
            std::cout << "default" << std::endl;
    }
}

main.cc:

#include "foo.h"
#include "enum.h"

int main()
{
    foo fu;
    fu.f(legacy::x);

    return 0;
}

Tenga en cuenta que el encabezado foo.h no tiene que saber nada sobre legacy::evil. Solo los archivos que utilizan el tipo heredado legacy::evil (aquí: main.cc) deben incluir enum.h.

1
mavam

Para VC, aquí está la prueba sobre la declaración en adelante 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; 
} 
 enumeración T: char 
 {
 A 
}; 

Pero recibió la advertencia para/W4 (/ W3 no incurrir en esta advertencia)

advertencia C4480: extensión no estándar utilizada: especificar el tipo subyacente para la enumeración 'T'

  1. VC (versión de compilador de optimización de C/C++ de 32 bits de Microsoft (R) 15.00.30729.01 para 80x86) no tiene errores en el caso anterior:

    • al ver la enumeración T; VC asume que el tipo de enumeración T usa 4 bytes por defecto como tipo subyacente, por lo que el código de ensamblado generado es:
? foo @@ YAXPAW4T @@@ Z PROC; foo 
; Archivo e:\work\c_cpp\cpp_snippet.cpp 
; Línea 13 
 Empuje ebp 
 Mov ebp, esp 
; Línea 14 
 Mov eax, DWORD PTR _tp $ [ebp] 
 Mov DWORD PTR [eax], 305419896; 12345678H 
; Línea 15 
 Pop ebp 
 Ret 0 
? Foo @@ YAXPAW4T @@@ Z ENDP; foo 

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

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]); 
 devuelve 0; 
} 

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

  • después de eliminar la declaración de enumeración T hacia adelante 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 clave 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 está sobrescribiendo

Por lo tanto, el uso de la declaración de enumeración hacia adelante 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 se habla con el sistema integrado, que está limitado en la memoria.

1
zhaorufei

Hay algo de disentimiento desde que esto fue golpeado (más o menos), así que aquí hay algunos puntos relevantes del estándar. La investigación muestra que la norma no define realmente la declaración hacia adelante, ni tampoco establece explícitamente que las enumeraciones pueden o no pueden ser declaradas hacia adelante.

Primero, de dcl.enum, sección 7.2:

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 la implementación qué tipo de integral se usa como tipo subyacente para una enumeración, excepto que el tipo subyacente no será mayor que int, a menos que el valor de un enumerador no se ajuste a un int o un int sin signo. Si la lista de enumeradores está vacía, el tipo subyacente es como si la enumeración tuviera un solo enumerador con valor 0. El valor de sizeof () aplicado a un tipo de enumeración, un objeto de tipo de enumeración o un enumerador, es el valor de sizeof () aplicado al tipo subyacente.

Por lo tanto, el tipo subyacente de una enumeración está definido por la implementación, con una restricción menor.

A continuación, pasamos a la sección sobre "tipos incompletos" (3.9), que es lo más cercano a cualquier norma sobre declaraciones a plazo:

Una clase que ha sido declarada pero no definida, o una matriz de tamaño desconocido o de tipo de elemento incompleto, es un tipo de objeto definido de manera incompleta.

Un tipo de clase (como "clase X") puede estar incompleto en un punto de una unidad de traducción y completarse más adelante; el tipo "clase X" es el mismo tipo en ambos puntos. El tipo declarado de un objeto de matriz puede ser una matriz de tipo de clase incompleta y, por lo tanto, incompleta; si el tipo de clase se completa más adelante en la unidad de traducción, el tipo de matriz se completa; el tipo de matriz en esos dos puntos es el mismo tipo. El tipo declarado de un objeto de matriz puede ser una matriz de tamaño desconocido y, por lo tanto, estar incompleto en un punto de una unidad de traducción y completarse más adelante; los tipos de matriz en esos dos puntos ("matriz de límite desconocido de T" y "matriz de N T") son tipos diferentes. El tipo de puntero a matriz de tamaño desconocido, o de un tipo definido por una declaración typedef para ser una matriz de tamaño desconocido, no se puede completar.

Así que allí, el estándar prácticamente estableció los tipos que pueden ser declarados hacia adelante. Enum no estaba allí, por lo que los autores de compiladores generalmente consideran que la declaración hacia adelante no está permitida por el estándar debido al tamaño variable de su tipo subyacente.

Tiene sentido, también. Normalmente se hace referencia a las enumeraciones en situaciones de valor, y el compilador de hecho necesitaría saber el tamaño de almacenamiento en esas situaciones. Dado que el tamaño del almacenamiento está definido por la implementación, muchos compiladores pueden optar por utilizar valores de 32 bits para el tipo subyacente de cada enumeración, momento en el cual es posible reenviarlos. Un experimento interesante podría ser intentar declarar una enumeración en visual studio y luego forzarla a usar un tipo subyacente mayor que sizeof (int) como se explicó anteriormente para ver qué sucede.

1
Dan Olson

Mi solución a tu problema sería:

1 - use int en lugar de enumeraciones: declare sus ints en un espacio de nombres 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 sus métodos son privados, nadie se metirá con los datos. Incluso podría ir más lejos para comprobar 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: crear una clase completa con instancias limitadas constantes, como hecho en Java. Reenvíe la clase y luego defínala en el archivo CPP, e instale solo los valores de enumeración. Hice algo así en C++, y el resultado no fue tan satisfactorio como se desea, ya que necesitaba algún código para simular una enumeración (construcción de copia, operador =, etc.).

3: Como se propuso anteriormente, utilice la enumeración declarada de forma privada. A pesar de que un usuario verá su definición completa, no podrá usarla ni utilizar 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 utilizando su clase.

Mi conjetura sería la solución 3 o 1.

0
paercebal