c++ - español - imprimir enum en c
Adelante declarando una enumeración en c++ (18)
¡Parece que no puede ser declarado en GCC!
Discusión interesante here
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? 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, 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 poner 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.
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 debe definirse completamente 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.
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 determinada), 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 diferente tamaño en algunas plataformas) por ejemplo).
Por lo tanto, el compilador ni siquiera puede permitirle declarar hacia adelante y declarar la enumeración y utilizar un puntero hacia ella, porque incluso allí, necesita el tamaño de la enumeración.
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 declarar una enum
en C ++?
No tu 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 de enum
en su archivo de origen. 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 de enum
sea cualquier tipo que se ajuste al rango de valores que tiene para su enum
.
Qué significa eso?
Esto significa que el tipo subyacente de su enum
no se puede determinar completamente hasta que tenga todos los valores de la enum
definidos. No puedes separar la declaración y la definición de tu 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
menos que el valor de un enumerador no se ajuste a unint
ounsigned 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 desizeof()
aplicado a un tipo de enumeración, un objeto de tipo de enumeración o un enumerador, es el valor desizeof()
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é pasa si especifica el tipo de su enum
explícitamente como esto:
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.
En mis proyectos, adopté la técnica de enumeración de espacios de nombres para tratar con las enum
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 usan el tipo legacy::evil
(aquí: main.cc) deben incluir enum.h
En respuesta a la aclaración: si usa la enum
solo internamente, ¿por qué no declararla como private
en la clase?
Estoy agregando una respuesta actualizada aquí, dados los últimos desarrollos.
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 en adelante ( -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.
Hay algo de disentimiento desde que esto fue golpeado (más o menos), así que aquí hay algunos bits relevantes de la norma. La investigación muestra que el estándar 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 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 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 NT") 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 se pueden declarar 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 que 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.
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 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.
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
menos que el valor de un enumerador no se ajuste a unint
ounsigned 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 desizeof()
aplicado a un tipo de enumeración, un objeto de tipo de enumeración o un enumerador, es el valor desizeof()
aplicado al tipo subyacente.
Dado que la persona que llama a la función debe conocer el tamaño 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 la 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
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.
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 const, 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 del hecho de que un usuario verá su definición completa, no podrá utilizarla 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.
Para VC, aquí está la prueba sobre la declaración de reenvío y la especificación del tipo subyacente:
- El siguiente código está compilado ok.
typedef int myint; enum T ; void foo(T * tp ) { * tp = (T)0x12345678; } enum T : char { A };
Pero recibió la advertencia para / W4 (/ W3 no incurrir en esta advertencia)
advertencia C4480: extensión no estándar utilizada: especificación del tipo subyacente para la enumeración ''T''
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 enum 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 ensamblaje 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 de ensamblaje anterior se extrae de /Fatest.asm directamente, no mi suposición personal. ¿Ves el mov DWORD PTR [eax], 305419896; 12345678H línea?
El siguiente fragmento de código lo prueba:
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 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 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 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 memoria.
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
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.
Solo teniendo en cuenta 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 en adelante. Bueno, usa 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, ya que uno desearía poder pasar el valor enum de la enumeración. Ni siquiera se podía tener un puntero a é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 próximo 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 puedes 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.
Usted define una enumeración para restringir los valores posibles de elementos del tipo a un conjunto limitado. Esta restricción se aplicará en el momento de la compilación.
Al declarar 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 poder beneficiarse de él.
Aunque el compilador está preocupado por el tamaño del tipo enumerado, la intención de la enumeración se pierde cuando usted lo reenvía.
[Mi respuesta es incorrecta, pero la he dejado aquí porque los comentarios son útiles].
La enumeración de declaración hacia adelante 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 tienen un tamaño consistente. La declaración de enums hacia adelante se proporciona como una extensión de lenguaje por Visual C ++, por ejemplo.