2008-12-16 31 views
65

me di cuenta de C++ no se compilará el siguiente:¿Por qué no puedo tener un miembro de estást estático no integral en una clase?

class No_Good { 
    static double const d = 1.0; 
}; 

Sin embargo, permitirá que felizmente una variante en la que se cambia el doble a un int, sin firmar, o cualquier tipo integral:

Mi solución era alterar para que se lea:

class Now_Good { 
    static double d() { return 1.0; } 
}; 

y averiguar que el compilador será lo suficientemente inteligente como para inline cuando sea necesario ... pero me dejó CUR io.

¿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:

pregunta ha sido contestada, pero para la finalización, el error que estaba viendo:

error C2864: 'd' : only const static integral data members can be initialized inside a class or struct 
+0

qué compilador/plataforma, o lo ves en múltiplos? – warren

+0

¿Qué mensaje de error está recibiendo en VS7.1? –

Respuesta

43

El problema es que con un entero, el compilador por lo general no tiene que crear siempre una dirección de memoria para el 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 las variables enteras constantes pero no los valores dobles constantes. A niveles de optimización más altos (por ejemplo, -O1), se incluyen dobles constantes. Por lo tanto, el código siguiente se compila en -O1 pero no a -O0: Línea

// 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); 
} 

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 un transporte máxima, puede declarar las constantes en los archivos de cabecera 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". .

+8

const estático doble d = 1.0; no es válido C++. no debe compilar en absoluto. esa forma solo está permitida para tipos integrales. esa es la razón por la que min y max en numeric_limits son funciones y no constantes estáticas. No estoy seguro de por qué se compila para ti. –

+3

parece ser una extensión de gcc. compilando con -rendimientos enérgicos: "foo.cpp: 4: error: ISO C++ prohíbe la inicialización de la constante miembro 'd' del tipo no integral 'const double'" –

+0

@ JohannesSchaub-litb: gracias por la investigación. esta respuesta debe ser editada en la parte superior para señalar eso. –

3

No sé por qué sería tratar una doble diferente de un int. Pensé que había usado esa forma antes. He aquí una solución alternativa:

class Now_Better 
{ 
    static double const d; 
}; 

Y en su archivo .cpp:

double const Now_Better::d = 1.0; 
+0

Sí, me alejé de esta posible solución solo porque creía que la mía era más fácil de leer. No me gusta * tener * para declarar e inicializar un valor en archivos separados (Mi clase está en .h). Gracias. –

15

veo ninguna razón técnica está prohibido

struct type { 
    static const double value = 3.14; 
}; 

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

Sin embargo, la versión de la próxima C++ permitirá que el uso de constexpr:

struct type { 
    static constexpr double value = 3.14; 
    static constexpr double value_as_function() { return 3.14; } 
}; 

y hará type::value una expresión constante. Mientras tanto, lo mejor es seguir el patrón también utilizado por std::numeric_limits:

struct type { 
    static double value() { return 3.14; } 
}; 

No volverá una expresión constante (valor no se conoce en tiempo de compilación), pero que sólo importa teórica, ya que la práctica de la el valor se incluirá de todos modos. Consulte la propuesta constexpr. Contiene

4.4

Floating-point constant expressions

Traditionally, evaluation of floating-point constant expression at compile-time is a thorny issue. For uniformity and generality, we suggest to allow constant-expression data of floating point types, initialized with any floating-point constant expressions. That will also increase compatibility with C99 [ISO99, §6.6] which allows

[#5] An expression that evaluates to a constant is required in several contexts. If a floating expression is evaluated in the translation envi- ronment, the arithmetic precision and range shall be at least as great as if the expression were being evaluated in the execution environ- ment.

+0

No entendí en silencio "Permitir que los parámetros de la plantilla de coma flotante trajeran su propio conjunto de reglas que realmente no valga la pena". También tiene dificultades para obtener "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". – Chubsdad

+2

@Chubsdad el primero se debe a la inexactitud habitual de los cálculos de coma flotante, que puede ser diferente de la implementación a la implementación. Mientras '1 + 1' siempre es' 2' en cualquier implementación, cálculos igualmente simples pueden arrojar resultados diferentes con matemática de coma flotante en diferentes modelos de punto flotante (cuentame, no soy aficionado a estos problemas, pero sí sé que existen) . –

+0

Para este último problema, realmente no sé cuál es el fundamento. Tenga en cuenta que un entorno de compilación puede ser diferente del entorno de ejecución para compiladores cruzados. El razonamiento aquí tal vez para asegurarse de que una gran precisión no empeore al calcular el resultado en tiempo de compilación. Pero tal vez puedas hacer una pregunta SO separada de esto. –

7

que realmente no da una razón de ser, pero esto es lo que BS tiene que decir acerca de esto en "The C++ Programming Language Third Edition":

10.4.6.2 Member Constants

It is also possible to initialize a static integral constant member by adding a constant-expression initializer to its member declaration. For example:

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 
    // ... 
}; 

However, an initialized member must still be (uniquely) defined somewhere, and the initializer may not be repeated:

const int Curious::c1; // necessary, but don't repeat initializer here 

I consider this a misfeature. When you need a symbolic constant within a class declaration, use an enumerator (4.8, 14.4.6, 15.3). For example:

class X { 
    enum { c1 = 7, c2 = 11, c3 = 13, c4 = 17 }; 
    // ... 
}; 

In that way, no member definition is needed elsewhere, and you are not tempted to declare variables, floating-point numbers, etc.

Y en el Apéndice C (Aspectos técnicos) en la Sección C.5 (expresiones constantes), BS tiene esto que decir acerca de "expresiones constantes":

In places such as array bounds (5.2), case labels (6.3.2), and initializers for enumerators (4.8), C++ requires a constant expression. A constant expression evaluates to an integral or enumeration constant. Such an expression is composed of literals (4.3.1, 4.4.1, 4.5.1), enumerators (4.8), and consts initialized by constant expressions. In a template, an integer template parameter can also be used (C.13.3). Floating literals (4.5.1) can be used only if explicitly converted to an integral type. Functions, class objects, pointers, and references can be used as operands to the sizeof operator (6.2) only.

Intuitively, constant expressions are simple expressions that can be evaluated by the compiler before the program is linked (9.1) and starts to run.

Nota que casi deja fuera de coma flotante como la posibilidad de 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".

+0

C++ 1x corregirá esa identidad incorrecta: (borrador reciente): "Un objeto o función no sobrecargada cuyo nombre aparece como una expresión potencialmente evaluada se utiliza a menos que sea un objeto que satisfaga los requisitos para aparecer en una expresión constante". statics solo debe definirse si se usa, por lo que esto lo resuelve –

+0

sin proporcionar definiciones en la mayoría de las implementaciones de hoy debido a esto: "Si un programa contiene una violación de una regla para la que no se requiere diagnóstico, esta Norma Internacional no exige ningún requisito implementaciones con respecto a ese programa ". –

+0

la regla que indica que debe proporcionar una definición se marca con "no se requiere diagnóstico". Entonces, la mayoría de las compilaciones (incluyendo Comeau) simplemente van bien a menos que tome la dirección del objeto y el objeto no se inicialice dentro de la definición de la clase, en cuyo momento se producirá un error de enlazador. –

-1

aquí se basa mi entendimiento sobre la declaración de BS acerca de la definición de su clase

A class is typically declared in a header file and a header file is typically included into many translation units. However, to avoid complicated linker rules, C++ requires that every object has a unique definition. That rule would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects.

http://www.stroustrup.com/bs_faq2.html#in-class

lo que, básicamente, esto no está permitido debido a que C++ no permiten esto. Para hacer las reglas del enlazador más simples, C++ requiere que cada objeto tenga una definición única.

miembro estático tiene solo una instancia en el ámbito de 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, para que el vinculador tenga que trabajar más para decidir qué miembro estático se debe usar como único a través de toda la traducción relacionada unidad.

Pero para las variables estáticas regulares, solo se pueden usar dentro de una unidad de traducción, incluso en el caso de 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 su clase para una configuración estática de tipo integral o de enumeración.

Cuestiones relacionadas