c++ - ejemplos - ¿Por qué no puedo tener un miembro estático no integral en una clase?
c++ online (5)
Noté que C ++ no compilará lo siguiente:
class No_Good {
static double const d = 1.0;
};
Sin embargo, permitirá con gusto una variación en la que el doble se cambie a un tipo entero, sin signo o integral:
class Happy_Times {
static unsigned const u = 1;
};
Mi solución fue alterarlo para leer:
class Now_Good {
static double d() { return 1.0; }
};
y supongo que el compilador será lo suficientemente inteligente como para entrar en línea cuando sea necesario ... pero me dejó con curiosidad.
¿Por qué los diseñadores de C ++ me permitirían crear const an int o unsigned, pero no un double?
Editar: Estoy usando Visual Studio 7.1 (.net 2003) en Windows XP.
Edit2:
La pregunta ha sido respondida, pero para completar, el error que estaba viendo:
error C2864: ''d'' : only const static integral data members can be initialized inside a class or struct
El problema es que con un número entero, el compilador generalmente no tiene que crear una dirección de memoria para la constante. No existe en tiempo de ejecución, y cada uso del mismo se inserta en el código circundante. Todavía puede decidir darle una ubicación de memoria, si alguna vez se toma su dirección (o si se pasa por referencia a una función), debe hacerlo. Para darle una dirección, necesita ser definida en alguna unidad de traducción. Y en ese caso, debe separar la declaración de la definición, ya que de lo contrario se definiría en múltiples unidades de traducción.
Al usar g ++ sin optimización ( -O0
), automáticamente se enlistan variables enteras constantes pero no valores dobles constantes. En niveles de optimización más altos (por ejemplo, -O1
), incorpora dobles constantes. Por lo tanto, el siguiente código se compila en -O1
pero NO en -O0
:
// File a.h
class X
{
public:
static const double d = 1.0;
};
void foo(void);
// File a.cc
#include <stdio.h>
#include "a.h"
int main(void)
{
foo();
printf("%g/n", X::d);
return 0;
}
// File b.cc
#include <stdio.h>
#include "a.h"
void foo(void)
{
printf("foo: %g/n", X::d);
}
Línea de comando:
g++ a.cc b.cc -O0 -o a # Linker error: ld: undefined symbols: X::d
g++ a.cc b.cc -O1 -o a # Succeeds
Para una portabilidad máxima, debe declarar sus constantes en los archivos de encabezado y definirlos una vez en algún archivo fuente. Sin optimización, esto no perjudicará el rendimiento, ya que no está optimizando de todos modos, pero con las optimizaciones habilitadas, esto puede perjudicar el rendimiento, ya que el compilador ya no puede incluir esas constantes en otros archivos fuente, a menos que active la "optimización de todo el programa". .
No sé por qué trataría un doble diferente de un int. Pensé que había usado esa forma antes. Aquí hay una solución alternativa:
class Now_Better
{
static double const d;
};
Y en su archivo .cpp:
double const Now_Better::d = 1.0;
No veo ninguna razón técnica por la cual
struct type {
static const double value = 3.14;
};
está prohibido. Cualquier ocasión que encuentre donde funciona se debe a características definidas de implementación no portátil. También parecen ser de uso limitado. Para las constantes integrales inicializadas en las definiciones de clase, puede usarlas y pasarlas a las plantillas como argumentos sin tipo, y usarlas como el tamaño de las dimensiones de la matriz. Pero no puede hacerlo para las constantes de punto flotante. Permitir que los parámetros de la plantilla de punto flotante traigan su propio conjunto de reglas realmente no valga la pena.
No obstante, la próxima versión de C ++ permitirá usar constexpr
:
struct type {
static constexpr double value = 3.14;
static constexpr double value_as_function() { return 3.14; }
};
Y hará que type::value
una expresión constante. Mientras tanto, tu mejor opción es seguir el patrón también utilizado por std::numeric_limits
:
struct type {
static double value() { return 3.14; }
};
No devolverá una expresión constante (el valor no se conoce en el momento de la compilación), pero eso solo tiene importancia teórica, ya que el valor práctico se insertará de todos modos. Ver la propuesta constexpr . Contiene
4.4
Floating-point constant expressions
Tradicionalmente, la evaluación de la expresión constante de coma flotante en tiempo de compilación es un tema espinoso. Para uniformidad y generalidad, sugerimos permitir datos de expresión constante de tipos de puntos flotantes, inicializados con cualquier expresión constante de coma flotante. Eso también aumentará la compatibilidad con C99 [ISO99, §6.6] que permite
[# 5] Se requiere una expresión que evalúe a una constante en varios contextos. Si se evalúa una expresión flotante en el entorno de traducción, la precisión aritmética y el rango serán al menos tan grandes como si la expresión se estuviera evaluando en el entorno de ejecución.
Realmente no da una explicación, pero esto es lo que Stroustrup tiene que decir sobre esto en "The C ++ Programming Language Third Edition":
10.4.6.2 Constantes de miembro
También es posible inicializar un miembro constante integral estático agregando un inicializador de expresión constante a su declaración de miembro. Por ejemplo:
class Curious { static const int c1 = 7; // ok, but remember definition static int c2 = 11; // error: not const const int c3 = 13; // error: not static static const int c4 = f(17); // error: in-class initializer not constant static const float c5 = 7.0; // error: in-class not integral // ... };
Sin embargo, un miembro inicializado todavía debe estar (únicamente) definido en alguna parte, y el inicializador puede no repetirse:
const int Curious::c1; // necessary, but don''t repeat initializer here
Considero que esto es un error. Cuando necesite una constante simbólica dentro de una declaración de clase, use un enumerador (4.8, 14.4.6, 15.3). Por ejemplo:
class X { enum { c1 = 7, c2 = 11, c3 = 13, c4 = 17 }; // ... };
De esta forma, no se necesita ninguna definición de miembro en otro lugar, y no está tentado a declarar variables, números de coma flotante, etc.
Y en el Apéndice C (Aspectos técnicos) en la Sección C.5 (Expresiones constantes), Stroustrup dice lo siguiente sobre "expresiones constantes":
En lugares tales como los límites de la matriz (5.2), las etiquetas de casos (6.3.2) y los inicializadores para los enumeradores (4.8), C ++ requiere una expresión constante . Una expresión constante se evalúa como una constante integral o de enumeración. Dicha expresión se compone de literales (4.3.1, 4.4.1, 4.5.1), enumeradores (4.8) y consts inicializados por expresiones constantes. En una plantilla, también se puede usar un parámetro de plantilla entera (C.13.3). Los literales flotantes (4.5.1) pueden usarse solo si se convierten explícitamente a un tipo integral. Las funciones, objetos de clase, punteros y referencias se pueden usar como operandos solo para el operador sizeof (6.2).
Intuitivamente, las expresiones constantes son expresiones simples que el compilador puede evaluar antes de que el programa esté vinculado (9.1) y comience a ejecutarse.
Tenga en cuenta que prácticamente deja de lado el punto flotante como para poder jugar en ''expresiones constantes''. Sospecho que ese punto flotante se dejó fuera de este tipo de expresiones constantes simplemente porque no son lo suficientemente "simples".
aquí está mi comprensión basada en la declaración de Stroustrup sobre la definición en clase
Por lo general, una clase se declara en un archivo de cabecera y, por lo general, un archivo de cabecera se incluye en muchas unidades de traducción. Sin embargo, para evitar reglas de enlazador complicadas, C ++ requiere que cada objeto tenga una definición única. Esa regla se rompería si C ++ permitiera la definición dentro de la clase de las entidades que debían almacenarse en la memoria como objetos.
http://www.stroustrup.com/bs_faq2.html#in-class
así que, básicamente, esto no está permitido porque C ++ no permite esto. Para hacer las reglas del enlazador más simples, C ++ requiere que cada objeto tenga una definición única.
El miembro estático tiene solo una instancia en el alcance de la clase, no como las variables estáticas regulares que se usan mucho en C, que tiene solo una instancia dentro de una unidad de traducción.
Si el miembro estático se define en clase, y la definición de clase se incluirá en muchas unidades de traducción, de modo que el vinculador tiene que trabajar más para decidir qué miembro estático se debe usar como el único a través de toda la unidad de traducción relacionada.
Pero para las variables estáticas regulares, solo se pueden usar dentro de una unidad de traducción, incluso en el caso de que diferentes variables estáticas en diferentes unidades de traducción con el mismo nombre, no se afectarán entre sí. Linker puede hacer un trabajo simple para vincular variables estáticas regulares dentro de una unidad de traducción.
para disminuir las complicaciones y dar la función base, C ++ proporciona la única definición en la clase para una configuración estática de tipo integral o de enumeración.