2009-10-28 13 views
47

Me gustaría definir una constante char * en mi archivo de cabecera para que use mi archivo .cpp. Así que he intentado esto:¿Cómo declarar un const char * estático en su archivo de encabezado?

private: 
    static const char *SOMETHING = "sommething"; 

que me lleva con el siguiente error del compilador:

error C2864: 'SomeClass::SOMETHING' : only static const integral data members can be initialized within a class

Soy nuevo en C++. ¿Que esta pasando aqui? ¿Por qué es esto ilegal? ¿Y cómo puedes hacerlo alternativamente?

+1

debe usar "estática const char * const ALGO" en su lugar, a menos que realmente desea poder reasignar ALGO para señalar otra cosa en tiempo de ejecución. – bk1e

Respuesta

57

Debe definir variables estáticas en una unidad de traducción, a menos que sean de tipos integrales.

En su cabecera:

private: 
    static const char *SOMETHING; 
    static const int MyInt = 8; // would be ok 

En el archivo .cpp:

const char *YourClass::SOMETHING = "something"; 

C++ estándar, 9.4.2/4:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression. In that case, the member can appear in integral constant expressions within its scope. The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.

+0

así que me es imposible asignar esto en el archivo de encabezado? Puedo hacer esto con otros tipos de datos, como DWORD. ¿Por qué es esto? – Mark

+3

Porque un puntero no es un tipo integral. Integral solo comprende los componentes integrados. –

+0

¿Por qué es este el caso? ¿Qué limitación ha causado que esta restricción sea limitada en C++? – Mark

15

El error es que no se puede Inicialice un static const char* dentro de la clase. Solo puedes inicializar las variables enteras allí.

tiene que declarar la variable miembro de la clase y, a continuación, inicializarlo fuera de la clase:

// encabezado del archivo

class Foo { 
    static const char *SOMETHING; 
    // rest of class 
}; 

// CPP archivo

const char *Foo::SOMETHING = "sommething"; 

Si esto parece molesto, piense que es porque la inicialización solo puede aparecer en una unidad de traducción. Si estaba en la definición de la clase, eso generalmente estaría incluido en varios archivos. Los enteros constantes son un caso especial (lo que significa que el mensaje de error quizás no sea tan claro como podría serlo), y los compiladores pueden reemplazar efectivamente los usos de la variable con el valor entero.

Por el contrario, una variable char* apunta a un objeto real en la memoria, que es necesario para realmente existir, y es la definición (incluida la inicialización) lo que hace que el objeto exista. La "regla de una definición" significa que, por lo tanto, no desea colocarla en un encabezado, porque entonces todas las unidades de traducción, incluido ese encabezado, contendrían la definición. No se pudieron vincular entre sí, aunque la cadena contiene los mismos caracteres en ambos, porque bajo las reglas actuales de C++ se han definido dos objetos diferentes con el mismo nombre, y eso no es legal. El hecho de que tengan los mismos personajes no lo hace legal.

+0

No necesita otra "estática" en el archivo cpp. – VictorCreator

1

Inicializador de constantes permitido por C++ Estándar solo para tipos de enumeración o integrales. Ver 9.4.2/4 para más detalles:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. The member shall still be defined in a name- space scope if it is used in the program and the namespace scope definition shall not contain an initializer.

Y 9.4.2/7:

Static data members are initialized and destroyed exactly like non-local objects (3.6.2, 3.6.3).

por lo que debe escribir en algún lugar de cpp:

const char* SomeClass::SOMETHING = "sommething"; 
3

Si eres usando Visual C++, puede hacerlo de forma no portátil usando sugerencias para el enlazador ...

// In foo.h... 

class Foo 
{ 
public: 
    static const char *Bar; 
}; 

// Still in foo.h; doesn't need to be in a .cpp file... 

__declspec(selectany) 
const char *Foo::Bar = "Blah"; 

__declspec(selectany) significa que a pesar de que Foo::Bar obtendrá declarado en múltiples archivos objeto, el enlazador sólo recoger uno.

Tenga en cuenta que esto sólo funcionará con la cadena de herramientas Microsoft. No esperes que esto sea portátil.

3

Hay un truco que puede utilizar con las plantillas para proporcionar archivo H únicas constantes.

(nota, este es un ejemplo feo, pero funciona textualmente en, al menos, en g ++ 4.6.1.)

(archivo values.hpp)

#include <string> 

template<int dummy> 
class tValues 
{ 
public: 
    static const char* myValue; 
}; 

template <int dummy> const char* tValues<dummy>::myValue = "This is a value"; 

typedef tValues<0> Values; 

std::string otherCompUnit(); // test from other compilation unit 

(main.cpp)

#include <iostream> 
#include "values.hpp" 

int main() 
{ 
    std::cout << "from main: " << Values::myValue << std::endl; 
    std::cout << "from other: " << otherCompUnit() << std::endl; 
} 

(other.cpp)

#include "values.hpp" 

std::string otherCompUnit() { 
    return std::string(Values::myValue); 
} 

Compilar (p. Ej. g ++ -o main.cpp principal other.cpp & & ./main) y ver dos unidades de compilación referencia a la misma constante declarada en un encabezado:

from main: This is a value 
from other: This is a value 

En MSVC, en su lugar puede ser capaz de utilizar __declspec (selectany)

Por ejemplo:

__declspec(selectany) const char* data = "My data"; 
+0

El truco de plantilla es incorrecto. Es ilegal proporcionar múltiples definiciones de una especialización explícita. Esto podría funcionar si el compilador no logra atraparlo, pero esto es una violación de la ODR, sin embargo. – AnT

+0

En realidad, la implementación correcta del truco de la plantilla usaría una declaración no especializada. – AnT

+1

¿Otro caso más en el que el compilador de MS no sigue todos los estándares? Debo admitir que nunca lo probé en GCC. – Torlack

24

Para responder a la pregunta de la OP acerca de por qué se permite únicamente con tipos enteros.

Cuando un objeto se usa como lvalue (es decir, como algo que tiene una dirección almacenada), debe cumplir la "regla de definición única" (ODR), es decir, debe definirse en una sola unidad de traducción . El compilador no puede y no decidirá en qué unidad de traducción se define ese objeto. Esta es su responsabilidad. Por definiendo ese objeto en alguna parte no está simplemente definiéndolo, en realidad le está diciendo al compilador que desea definirlo aquí, en esta unidad de traducción específica.

Mientras tanto, en lenguaje C++ constantes integral tienen un estatus especial. Pueden formar expresiones de constante integrales (ICE). En ICE constantes integrales se utilizan como valores ordinarios, no como objetos (es decir, no es relevante si dicho valor de la integral tiene la dirección en el almacenamiento o no). De hecho, los ICE se evalúan en tiempo de compilación. Para facilitar tal uso de las constantes integrales, sus valores deben ser visibles globalmente. Y la constante en sí misma no necesita un lugar real en el almacenamiento. Debido a que estas constantes integrales recibieron un trato especial: se permitió incluir sus inicializadores en el archivo de encabezado, y el requisito de proporcionar una definición se relajó (primero de facto, luego de jure).

Otros tipos constantes no tiene tales propiedades. Otros tipos constantes casi siempre se usan como lvalues ​​(o al menos no pueden participar en ICE o algo similar a ICE), lo que significa que requieren una definición. El resto sigue.

+0

excepto plantillas –

+1

@Mooing Duck: El texto completo de ODR es bastante extenso. Tiene muchas "excepciones" y "no excepciones", que no están ni remotamente limitadas a "plantillas". Mi respuesta está restringida a una pequeña porción relevante de ODR que trata con constantes integrales. No veo cómo su comentario "excepto plantillas" puede ser relevante aquí (ni siquiera entiendo qué quiere decir exactamente con esa observación). – AnT

+1

Eso es todo cierto, y di un +1 porque esta es una gran respuesta, pero las clases de plantilla son (a mi conocimiento) excepciones a todas las reglas ODR. Solo pensé en tirar eso allí. –

0

Para responder al por qué pregunta, los tipos integrales son especiales en el sentido de que no son una referencia a un objeto asignado sino valores que se duplican y se copian. Es solo una decisión de implementación realizada cuando se definió el idioma, que era manejar valores fuera del sistema de objetos y de la manera más eficiente e "en línea" posible.

Esto no explica exactamente por qué están permitidos como inicializadores en un tipo, pero piensa que es esencialmente un #define y luego tendrá sentido como parte del tipo y no como parte del objeto.

9
class A{ 
public: 
    static const char* SOMETHING() { return "something"; } 
}; 

Lo hago todo el tiempo, especialmente para los parámetros predeterminados const caros.

class A{ 
    static 
    const expensive_to_construct& 
    default_expensive_to_construct(){ 
     static const expensive_to_construct xp2c(whatever is needed); 
     return xp2c; 
    } 
}; 
6

con C++ 11 se puede utilizar la palabra clave constexpr y escribir en su cabecera:

private: 
    static constexpr const char* SOMETHING = "something"; 


Notas:

  • constexpr hace SOMETHING un puntero constante por lo que no puede escribir

    SOMETHING = "something different"; 
    

    más adelante.

  • Dependiendo de su compilador, es posible que también tenga que escribir una definición explícita en el archivo .cpp:

    constexpr const char* MyClass::SOMETHING; 
    
Cuestiones relacionadas