studio - Clase de enumeración C++ 11 definida por el usuario Constructor predeterminado
programacion android pdf 2018 (3)
Sé que esta pregunta está fechada y que ya tiene una respuesta aceptada, pero aquí hay una técnica que podría ayudar en una situación como esta con algunas de las características más nuevas de C ++.
Puede declarar la variable de esta clase ya sea non static
o static
, se puede hacer de varias maneras permitidas en el soporte de su compilador actual.
No estático:
#include <iostream>
#include <array>
template<unsigned... IDs>
class PinIDs {
private:
const std::array<unsigned, sizeof...(IDs)> ids { IDs... };
public:
PinIDs() = default;
const unsigned& operator[]( unsigned idx ) const {
if ( idx < 0 || idx > ids.size() - 1 ) {
return -1;
}
return ids[idx];
}
};
Estático: hay tres formas de escribir esto: (Primero uno: C ++ 11 o 14 o superior) el último 2 (c ++ 17).
No me cites en la parte de C ++ 11; No estoy muy seguro de cuándo se introdujeron por primera vez las plantillas variadas o los paquetes de parámetros.
template<unsigned... IDs>
class PinIDs{
private:
static const std::array<unsigned, sizeof...(IDs)> ids;
public:
PinIDs() = default;
const unsigned& operator[]( unsigned idx ) const {
if ( idx < 0 || idx > ids.size() - 1 ) {
return -1;
}
return ids[idx];
}
};
template<unsigned... IDs>
const std::array<unsigned, sizeof...(IDs)> PinIDs<IDs...>::ids { IDs... };
template<unsigned... IDs>
class PinIDs{
private:
static constexpr std::array<unsigned, sizeof...(IDs)> ids { IDs... };
public:
PinIDs() = default;
const unsigned& operator[]( unsigned idx ) const {
if ( idx < 0 || idx > ids.size() - 1 ) {
return -1;
}
return ids[idx];
}
};
template<unsigned... IDs>
class PinIDs{
private:
static inline const std::array<unsigned, sizeof...(IDs)> ids { IDs... };
public:
PinIDs() = default;
const unsigned& operator[]( unsigned idx ) const {
if ( idx < 0 || idx > ids.size() - 1 ) {
return -1;
}
return ids[idx];
}
};
Todos los ejemplos anteriores, ya sea trabajo no estático o estático con el mismo caso de uso a continuación y proporcionan los resultados correctos:
int main() {
PinIDs<4, 17, 19> myId;
std::cout << myId[0] << " ";
std::cout << myId[1] << " ";
std::cout << myId[2] << " ";
std::cout << "/nPress any key and enter to quit." << std::endl;
char c;
std::cin >> c;
return 0;
}
Salida
4 17 19
Press any key and enter to quit.
Con este tipo de plantilla de clase que usa una lista de parámetros variadic, no tiene que usar ningún constructor sino el predeterminado. Agregué los límites de verificación en la matriz para que el operator[]
no exceda los límites de su tamaño; Podría haber lanzado un error, pero con el tipo unsigned
simplemente devolví -1 como un valor no válido.
Con este tipo, no hay valor predeterminado, ya que tiene que crear una instancia de este tipo de objeto a través de la lista de parámetros de la plantilla con un solo valor o conjunto de valores. Si uno quiere, pueden specialize this class
con un solo parámetro de 0
para un tipo predeterminado. Cuando instancias este tipo de objeto; Es definitivo ya que en él no se puede cambiar de su declaración. Este es un objeto constante y todavía se puede construir por defecto.
¿Hay una manera de especificar el constructor predeterminado de una enum class
?
Estoy utilizando una enum class
para especificar un conjunto de valores que son permitidos para un tipo de datos en particular en una biblioteca: en este caso, son los números de identificación de pin GPIO de una Raspberry Pi. Se ve algo como esto:
enum class PinID : int {N4 = 4, N17 = 17, /* ...etc... */ }
El objetivo de hacer esto en lugar de usar, digamos, un int
es asegurarnos de que el código sea seguro: puedo static_assert
(o, de lo contrario, el tiempo de compilación, el método utilizado no es importante para mí) cosas como las que alguien no tiene No cometí un error de ortografía (pasar un 5 en lugar de un 4, etc.), y recibo mensajes de error automáticos para los desajustes de tipo, etc.
El problema entonces es que la enum class
tiene un constructor predeterminado que, por el bien de la compatibilidad con los enum
de C, asumo (ya que tienen el mismo comportamiento), se inicializa a la enum class
equivalente a 0
. En este caso, no hay valor 0
. Esto significa que un usuario que realiza una declaración / definición como:
PinID pid = PinID();
es obtener un enumerador que no está explícitamente definido (y que ni siquiera parece "existir" cuando uno mira el código), y puede provocar errores de tiempo de ejecución. Esto también significa que las técnicas como switch
los valores de los enumeradores explícitamente definidos son imposibles sin tener un caso de error / predeterminado, algo que quiero evitar, ya que me obliga a throw
o hacer algo como devolver un boost::optional
, Que son menos susceptibles al análisis estático.
Traté de definir un constructor por defecto en vano. Intenté (desesperadamente) definir una función que comparte el nombre de la enum class
, pero esto (de manera bastante sorprendente) resultó en errores extraños del compilador. Quiero conservar la capacidad de convertir la enum class
a int
, con todos los enumeradores de N#
asignados a sus respectivos #
, por lo que simplemente "definir", por ejemplo, N4 = 0 es inaceptable; Esto es por simplicidad y cordura.
Supongo que mi pregunta es doble: ¿hay alguna manera de obtener el tipo de seguridad estática que uso después de usar la enum class
? Si no, ¿qué otras posibilidades preferirías? Lo que quiero es algo que:
- es construible por defecto
- se puede hacer para construir por defecto a un valor válido arbitrario
- proporciona el "conjunto finito de valores especificados" proporcionados por las
enum class
es - Es al menos tan seguro como una
enum class
- (preferiblemente) no implica polimorfismo de tiempo de ejecución
La razón por la que quiero la boost::lexical_cast
construcción predeterminada es porque planeo usar boost::lexical_cast
para reducir la sobrecarga sintáctica involucrada en las conversiones entre los valores de enum class
y la string
real asociada s que boost::lexical_cast
al sistema operativo (sysfs en este caso); boost::lexical_cast
requiere constructibilidad por defecto.
Los errores en mi razonamiento son bienvenidos: estoy empezando a sospechar que las enum class
son el objeto correcto para el trabajo incorrecto, en este caso; Se ofrecerá una aclaración si se solicita. Gracias por tu tiempo.
Un tipo definido con enum class
o enum struct
no es una clase sino una enumeración de ámbito y no puede tener un constructor predeterminado definido. El estándar C ++ 11 define que su PinID pid = PinID();
La instrucción dará una inicialización cero. Donde PinID
se definió como una enum class
. También permite que los tipos de enumeración en general mantengan valores distintos a las constantes del enumerador.
Para comprender que PinID () da una inicialización cero, es necesario leer las secciones estándar 3.9.9, 8.5.5, 8.5.7 y 8.5.10 juntas:
8.5.10 - An object whose initializer is an empty set of parentheses, ie, (), shall be value-initialized
8.5.7 - To value-initialize an object of type T means:
... de lo otherwise, the object is zero-initialized.
8.5.5 - To zero-initialize an object or reference of type T means: — if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;
3.9.9 : indica que los tipos de enumeración forman parte del conjunto de tipos conocidos como tipos escalares.
Una posible solución:
Para cumplir con los puntos 1 a 5, puede escribir una clase de la siguiente manera:
class PinID
{
private:
PinID(int val)
: m_value(val)
{}
int m_value;
public:
static const PinID N4;
static const PinID N17;
/* ...etc... */
PinID()
: m_value(N4.getValue())
{}
PinID(const PinID &id)
: m_value(id.getValue())
{}
PinID &operator = (const PinID &rhs)
{
m_value = rhs.getValue();
return *this;
}
int getValue() const
{
return m_value;
}
// Attempts to create from int and throw on failure.
static PinID createFromInt(int i);
friend std::istream& operator>>(std::istream &is, PinID &v)
{
int candidateVal(0);
is >> candidateVal;
v = PinID::createFromInt(candidateVal);
return is;
}
};
const PinID PinID::N4 = PinID(4);
/* ...etc... */
Eso puede darle algo en lo que tendría que hacer esfuerzos específicos para obtener valores no válidos. El constructor y el operador de flujo predeterminados deben permitir que funcione con lexical_cast.
Parece que depende de la importancia de las operaciones en un PinID después de su creación, ya sea que valga la pena escribir una clase o simplemente manejar los valores no válidos en todas partes a medida que se usa el valor.
Una enum class
es solo una enum
fuertemente enum
; no es una class
C ++ 11 acaba de reutilizar la palabra clave existente de la class
para evitar la introducción de una nueva palabra clave que rompería la compatibilidad con el código de C ++ heredado.
En cuanto a su pregunta, no hay forma de garantizar en el momento de la compilación que un elenco involucre a un candidato adecuado. Considerar:
int x;
std::cin >> x;
auto p = static_cast<PinID>(x);
Esto es perfectamente legal y no hay manera de asegurar estáticamente que el usuario de la consola haya hecho lo correcto.
En su lugar, deberá verificar en tiempo de ejecución si el valor es válido. Para solucionar esto de manera automatizada, uno de mis compañeros de trabajo creó un generador de enum
que construye estas verificaciones además de otras rutinas útiles dado un archivo con valores de enumeración. Necesitará encontrar una solución que funcione para usted.