Reenviar la declaración de una enumeración en C ++

265

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?

Aclaración 2: estoy haciendo esto ya que tengo métodos privados en una clase que toman 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
}

ya que el proyecto X no es algo que quiero que mis usuarios sepan.

Entonces, 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 el cpp y distribuir el archivo y el encabezado de la biblioteca construida a las personas.

En cuanto al compilador, es GCC.

szevvy
fuente
Tantos años después de esto y de alguna manera StackOverflow me atrajo;) Como una sugerencia post mortem, simplemente no hagas esto especialmente en el escenario que describas. Preferiría definir una interfaz abstracta y exponer esto a los usuarios y mantener la definición de enumeración y todos los demás detalles de implementación con la implementación interna que nadie más ve de mi lado, lo que me permite hacer lo que sea y tener el control total de cuándo los usuarios ven cualquier cosa.
RnR
Si lees más allá de la respuesta aceptada, esto es completamente posible desde C ++ 11.
fuzzyTew

Respuestas:

217

La razón por la que no se puede declarar la enumeración en adelante es que sin conocer los valores, el compilador no puede conocer 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 directa, 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 del Estándar ISO C ++:

El tipo subyacente de una enumeración es un tipo integral que puede representar todos los valores de enumerador definidos en la enumeración. Se define la implementación del tipo integral que se usa como el tipo subyacente para una enumeración, excepto que el tipo subyacente no será mayor que a intmenos que el valor de un enumerador no pueda caber en un into unsigned int. Si la lista de enumeradores 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 de enumeración o un enumerador es el valor de sizeof()aplicado a tipo subyacente

Dado que la persona que llama 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 conocerse antes del prototipo de la función.

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

KJAWolf
fuente
29
-1. Su razonamiento no puede ser correcto; de lo contrario, ¿por qué se le permite declarar hacia adelante "clase C"; y luego declarar un prototipo de función que toma o devuelve una C, antes de definir completamente C?
j_random_hacker
112
@j_random: no puede usar una clase antes de que esté completamente definida; solo puede usar un puntero o una referencia a esa clase y eso se debe a que sus tamaños y formas de operaciones no dependen de qué clase sea.
RnR
27
El compilador establece el tamaño de una referencia o puntero a un objeto de clase, y es independiente del tamaño real del objeto: es el tamaño de los punteros y las referencias. La enumeración es un objeto, y su tamaño es necesario para que el compilador acceda al almacenamiento correcto.
KJAWolf
17
Lógicamente, sería posible declarar punteros / referencias a las enumeraciones si tuviéramos declaraciones de enumeración hacia adelante, tal como podemos hacer con las clases. Es solo que no suele tratar con punteros a enumeraciones :)
Pavel Minaev
20
Sé que esta discusión terminó hace mucho tiempo, pero tengo que alinearme con @j_random_hacker aquí: el problema aquí no es sobre el puntero o la referencia a tipos incompletos, sino sobre el uso de tipos incompletos en las declaraciones. Como es legal hacerlo struct S; void foo(S s);(tenga en cuenta que foosolo se declara, no se define), entonces no hay ninguna razón por la que no podamos hacerlo enum E; void foo(E e);también. En ambos casos, el tamaño no es necesario.
Luc Touraille el
201

La declaración directa de enumeraciones es posible desde C ++ 11. Anteriormente, la razón por la que los tipos de enumeración no se podían declarar hacia adelante es porque el tamaño de la enumeración depende de su contenido. Mientras la aplicación especifique el tamaño de la enumeración, se puede declarar hacia adelante:

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.
usuario119017
fuente
1
¿Existe algún soporte de compilación para esta función? Parece que GCC 4.5 no lo tiene :(
rubenvb
44
@rubenvb También lo hace Visual C ++ 11 (2012) blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
knatten
Estaba buscando enum32_t y con tu respuesta enum XXX: uint32_t {a, b, c};
fantastory
¿Pensé que las enumeraciones de ámbito (clase enum) se implementaron en C ++ 11? Si es así, ¿cómo son legales en C ++ 0X?
Terrabits
1
C ++ 0x era el nombre de trabajo para C ++ 11, @Terrabits, antes de que se estandarizara oficialmente. La lógica es que si se sabe (o es muy probable) que una característica se incluya en un estándar actualizado, entonces el uso de esa característica antes de que el estándar se lance oficialmente tiende a usar el nombre de trabajo. (Por ejemplo, los compiladores que admitían características de C ++ 11 antes de la estandarización oficial en 2011 tenían compatibilidad con C ++ 0x, los compiladores que admitían funciones de C ++ 17 antes de la estandarización oficial tenían compatibilidad con C ++ 1z y los compiladores que admitían características de C ++ 20 en este momento (2019) tiene soporte para C ++ 2a.)
Justin Time - Restablece Monica el
79

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

Puede declarar hacia adelante 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 versiones posteriores ( -std=c++0xo -std=c++11en versiones más recientes). Visual C ++ 2013 lo admite; 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.

Tom
fuente
44
+1 porque esta es la única respuesta que menciona que necesita declarar el tipo en su declaración, así como su definición.
turoni
Creo que el soporte parcial en el MSVC temprano fue respaldado desde C ++ / CLI enum classcomo una extensión de C ++ (antes de C ++ 11 diferente enum class), al menos si no recuerdo mal. El compilador le permitía especificar el tipo subyacente de una enumeración, pero no enum classadmitía enumeraciones declaradas hacia adelante, y le advirtió que calificar un enumerador con el alcance de la enumeración era una extensión no estándar. Recuerdo que funciona más o menos lo mismo que especificar el tipo subyacente en C ++ 11, excepto que es más molesto porque tuvo que suprimir la advertencia.
Justin Time - Restablece a Mónica el
30

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

Pero, ¿puedes reenviar una declaración enumen C ++?

No, no puedes.

¿Pero por qué no permitirlo? Si se permitiera, podría definir su enumtipo en su archivo de encabezado y sus enumvalores en su archivo de origen. Parece que debería permitirse, ¿verdad?

Incorrecto.

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

Qué significa eso?

Significa que el enumtipo subyacente de su no puede determinarse completamente hasta que tenga todos los valores enumdefinidos. En qué hombre no puede separar la declaración y la definición de su enum. Y, por lo tanto, no puede reenviar una declaración enumen 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 de enumerador definidos en la enumeración. Se define la implementación del tipo integral que se usa como el tipo subyacente para una enumeración, excepto que el tipo subyacente no será mayor que a intmenos que el valor de un enumerador no pueda caber en un into unsigned int. Si la lista de enumeradores 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 de enumeración o un enumerador es el valor de sizeof()aplicado a tipo subyacente

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

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

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

¿Puedes entonces declarar tu enum?

No. ¿Pero por qué no?

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

Fuente

Brian R. Bondy
fuente
15
Esta respuesta ahora está varios años desactualizada.
Tom
El tiempo nos hace tontos a todos. Su comentario ahora está varios años desactualizado; la respuesta una década!
pjcard
14

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

La declaración directa de enumeraciones no es estándar, porque no se garantiza que los punteros a diferentes tipos de enumeración sean del 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 las enumeraciones tienen un tamaño constante. La declaración directa de enumeraciones se proporciona como una extensión de lenguaje por Visual C ++, por ejemplo.

James Hopkin
fuente
2
-1. Si su razonamiento era correcto, el mismo razonamiento implicaría que las declaraciones directas de los tipos de clase no podrían usarse para crear punteros a esos tipos, pero sí pueden.
j_random_hacker
66
+1. El razonamiento es correcto. El caso específico es plataformas donde sizeof (char *)> sizeof (int *). Ambos pueden ser tipos subyacentes para una enumeración, dependiendo del rango. Las clases no tienen tipos subyacentes, por lo que la analogía es falsa.
MSalters
3
@MSalters: Ejemplo: "struct S {int x;};" Ahora, sizeof (S *) debe ser igual al tamaño de cualquier otro puntero a estructura, ya que C ++ permite que dicho puntero se declare y utilice antes de la definición de S ...
j_random_hacker
1
@MSalters: ... En una plataforma donde sizeof (char *)> sizeof (int *), el uso de un puntero de "tamaño completo" para esta estructura en particular puede ser ineficiente, pero simplifica drásticamente la codificación, y exactamente lo mismo Lo que podría y debería hacerse para los tipos de enumeración.
j_random_hacker
44
los punteros a los datos y los punteros a las funciones pueden ser de diferentes tamaños, pero estoy bastante seguro de que los punteros de datos tienen que hacer un viaje de ida y vuelta (emitir a otro tipo de puntero de datos, luego volver al original, aún deben funcionar), lo que implica que todos Los punteros de datos son del mismo tamaño.
Ben Voigt
7

De hecho, no hay tal cosa como una declaración anticipada de enumeración. 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 único uso de su enumeración es por funciones de miembros privados, puede implementar la encapsulación teniendo la enumeración como un miembro privado de esa clase. La enumeración aún debe definirse por completo 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 miembros privados allí, y no es una exposición peor de los aspectos internos de implementación que eso.

Si necesita un mayor grado de ocultación de los detalles de su implementación, puede dividirlo en una interfaz abstracta, que solo consta de funciones virtuales puras y 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, y mucho menos sus funciones privadas, no se expondrá.

Alexey Feldgendler
fuente
5

Solo notando que la razón es que el tamaño de la enumeración aún no se conoce después de la declaración hacia adelante. Bueno, utiliza la declaración hacia adelante de una estructura para poder pasar un puntero o hacer referencia a un objeto desde un lugar al que también se hace referencia en la definición de estructura declarada hacia adelante.

Declarar adelante una enumeración no sería demasiado útil, porque uno desearía poder pasar la enumeración por valor. Ni siquiera podría tener un puntero, porque recientemente me dijeron que algunas plataformas usan punteros de diferente tamaño para char que para int o long. Entonces 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 próximo estándar C ++ debido al 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 más adelante en una redeclaración posterior de la enumeración. Ver 7.2en el borrador de trabajo actual.

Johannes Schaub - litb
fuente
4

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 se compile a un largo, por lo que es intercambiable con E.

Laurie Cheers
fuente
1
Por supuesto, esto significa que (A) los tipos de E y Econtent difieren, y (B) en sistemas LP64, sizeof (E) = 2 * sizeof (EContent). Solución trivial: ULONG_MAX, también más fácil de leer.
MSalters
2

Si realmente no desea que su enumeración aparezca en su archivo de encabezado Y se asegure de que solo se use por métodos privados, entonces una solución puede ser seguir el principio de 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 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, no es necesario volver a compilar otras clases al cambiar la implementación de una clase. Otra es que acelera su tiempo de compilación porque sus encabezados son muy simples.

Pero es difícil de usar, por lo que realmente debería preguntarse si declarar su enumeración como privada en el encabezado es un problema.

Vincent Robert
fuente
3
struct AImpl; struct A {privado: AImpl * pImpl; };
2

Puede envolver la enumeración en una estructura, agregando algunos constructores y conversiones de tipo, y en su lugar declarar 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

Leszek Swirski
fuente
1

¡Parece que no se puede declarar hacia adelante en GCC!

Interesante discusión aquí

prakash
fuente
1

Hay un poco de desacuerdo ya que esto fue golpeado (más o menos), así que aquí hay algunos bits relevantes del estándar. La investigación muestra que el estándar no define realmente la declaración hacia adelante, ni declara 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 de enumerador definidos en la enumeración. Se define la implementación del tipo integral que se usa como el tipo subyacente para una enumeración, excepto que el tipo subyacente no debe 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 único 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 casi tan cercana como llegamos a cualquier estándar sobre declaraciones a futuro:

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 incompletamente definido.

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 podría 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 una matriz de tamaño desconocido, o de un tipo definido por una declaración typedef como una matriz de tamaño desconocido,

Entonces, allí, el estándar presentaba más o menos los tipos que se pueden declarar hacia adelante. Enum no estaba allí, por lo que los autores del compilador generalmente consideran que la declaración no está permitida por el estándar debido al tamaño variable de su tipo subyacente.

Tiene sentido también. Las enumeraciones generalmente se mencionan en situaciones de valor, y el compilador realmente necesitaría saber el tamaño de almacenamiento en esas situaciones. Dado que el tamaño de almacenamiento está definido por la implementación, muchos compiladores pueden optar por usar valores de 32 bits para el tipo subyacente de cada enumeración, momento en el que es posible declararlos hacia adelante. 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.

Dan Olson
fuente
tenga en cuenta que explícitamente no permite "enum foo"; en 7.1.5.3/1 (pero como con todo, siempre que el compilador advierta, aún puede compilar dicho código, por supuesto)
Johannes Schaub - litb
Gracias por señalarlo, ese es un párrafo realmente esotérico y podría llevarme una semana analizarlo. Pero es bueno saber que está ahí.
Dan Olson
no se preocupe. algunos párrafos estándar son realmente extraños :) bueno, un especificador de tipo elaborado es algo en el que especifica un tipo, pero también especifica algo más para que no sea ambiguo. por ejemplo, "struct X" en lugar de "X", o "enum Y" en lugar de solo "Y". Lo necesita para afirmar que algo es realmente un tipo.
Johannes Schaub - litb
así que puedes usarlo así: "clase X * foo;" si X aún no se ha declarado hacia adelante. o "typename X :: foo" en una plantilla para desambiguación. o "class link obj;" si hay una función "enlace" en el mismo ámbito que sombrearía la clase que tiene el mismo nombre.
Johannes Schaub - litb
en 3.4.4 dice que se usan si algún nombre que no es de tipo oculta un nombre de tipo. ahí es donde se usan con mayor frecuencia, aparte de declarar hacia adelante como "clase X"; (aquí es el único constituyente de una declaración). habla de ellos en no plantillas aquí. sin embargo, 14.6 / 3 enumera su uso en plantillas.
Johannes Schaub - litb
1

Para VC, aquí está 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;
    enumeración T;
    nulo foo (T * tp)
    {
        * tp = (T) 0x12345678;
    }
    enumeración T: char
    {
        UNA
    };

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

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

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

    • al ver enum T; VC supone que el tipo de enumeración T usa 4 bytes predeterminados int 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
        empujar 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 directamente de /Fatest.asm, 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)
    {
        Unión {
            char ca [4];
            T t;
        }una;
        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 hacia adelante 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 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 sobrescribe

Por lo tanto, el uso de la declaración directa de enumeración 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, descubrí que vale la pena guardar 3 bytes especificando el tipo subyacente como char cuando se habla con el sistema incorporado, que tiene memoria limitada.

zhaorufei
fuente
1

En mis proyectos, adopté la técnica de enumeración de espacio de nombres 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 foo.hencabezado no tiene que saber nada legacy::evil. Solo se legacy::evildeben incluir los archivos que usan el tipo heredado (aquí: main.cc) enum.h.

mavam
fuente
0

Mi solución a su problema sería:

1 - use int en lugar de enumeraciones: declare sus entradas 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 interferirá 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: cree una clase completa con instancias de const limitadas, como en Java. Reenvíe la clase, y luego defínala en el archivo CPP, e instancia solo los valores de tipo enum. Hice algo así en C ++, y el resultado no fue tan satisfactorio como se deseaba, ya que necesitaba un código para simular una enumeración (construcción de copia, operador =, etc.).

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

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

paercebal
fuente
-1

Debido a que 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 declarar hacia adelante la enumeración y usar un puntero al usuario, porque incluso allí, necesita el tamaño de la enumeración.

Carl Seleborg
fuente
-1

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

Cuando se declara hacia adelante 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 cuando la reenvía.

xtofl
fuente
1
No, el código posterior no necesita conocer los valores para que esto sea útil, en particular, si el código posterior es simplemente un prototipo de función que toma o devuelve parámetros enum, el tamaño del tipo no es importante. El uso de la declaración directa aquí puede eliminar las dependencias de compilación, acelerando la compilación.
j_random_hacker
Tienes razón. La intención no es obedecer los valores, sino el tipo. Resuelto con 0x tipos Enum.
xtofl