and - const c++
¿Cómo declarar un static const char*en su archivo de encabezado? (9)
Me gustaría definir una char * constante en mi archivo de encabezado para que use mi archivo .cpp. Así que he intentado esto:
private:
static const char *SOMETHING = "sommething";
Lo que me trae el siguiente error de compilación:
error C2864: ''SomeClass :: SOMETHING'': solo miembros de datos integrales const pueden inicializarse dentro de una clase
Soy nuevo en C ++. ¿Que esta pasando aqui? ¿Por qué es esto ilegal? ¿Y cómo puedes hacerlo alternativamente?
Con C ++ 11 puede usar la palabra clave constexpr
y escribir en su encabezado:
private:
static constexpr const char* SOMETHING = "something";
Notas:
constexpr
haceSOMETHING
un puntero constante para que no pueda escribirSOMETHING = "something different";
mas tarde.
Dependiendo de su compilador, también podría necesitar escribir una definición explícita en el archivo .cpp:
constexpr const char* MyClass::SOMETHING;
El error es que no puede inicializar un static const char*
dentro de la clase. Solo puedes inicializar las variables enteras allí.
Debe declarar la variable miembro en la clase y luego inicializarla fuera de la clase:
// archivo de cabecera
class Foo {
static const char *SOMETHING;
// rest of class
};
// archivo cpp
const char *Foo::SOMETHING = "sommething";
Si esto parece molesto, piense que se debe a que 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 se requiere para existir realmente, 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.
Hay un truco que puede usar con plantillas para proporcionar constantes de archivo H solamente.
(nota, este es un ejemplo feo, pero funciona al pie de la letra 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);
}
Compila (por ejemplo, g ++ -o main main.cpp other.cpp && ./main) y ve dos unidades de compilación que hacen 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 usar __declspec (selectany)
Por ejemplo:
__declspec(selectany) const char* data = "My data";
Inicializador constante permitido por C ++ Standard solo para tipos integrales o de enumeración. Ver 9.4.2 / 4 para detalles:
Si un miembro de datos estático es de tipo const integral o const enumeration, su declaración en la definición de clase puede especificar un constante-inicializador que debe ser una expresión de constante integral (5.19). En ese caso, el miembro puede aparecer en expresiones constantes integrales. El miembro aún se definirá en un ámbito de espacio de nombre si se utiliza en el programa y la definición de ámbito de espacio de nombre no contendrá un inicializador.
Y 9.4.2 / 7:
Los miembros de datos estáticos se inicializan y se destruyen exactamente como los objetos no locales (3.6.2, 3.6.3).
Entonces deberías escribir en algún lugar del archivo cpp:
const char* SomeClass::SOMETHING = "sommething";
Necesita definir variables estáticas en una unidad de traducción, a menos que sean de tipos integrales.
En tu encabezado:
private:
static const char *SOMETHING;
static const int MyInt = 8; // would be ok
En el archivo .cpp:
const char *YourClass::SOMETHING = "something";
Estándar C ++, 9.4.2 / 4:
Si un miembro de datos estáticos es de tipo const integral o const enumeration, su declaración en la definición de clase puede especificar un constante-inicializador que será una expresión constante integral. En ese caso, el miembro puede aparecer en expresiones constantes integrales dentro de su alcance. El miembro aún se definirá en un ámbito de espacio de nombres si se usa en el programa y la definición del alcance del espacio de nombres no contendrá un inicializador.
Para responder a la pregunta por qué , los tipos integrales son especiales porque no son una referencia a un objeto asignado, sino más bien 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 piensan que es esencialmente un #define
y luego tendrá sentido como parte del tipo y no como parte del objeto.
Para responder la pregunta del OP sobre por qué solo está permitido con tipos integrales.
Cuando un objeto se utiliza como un valor l (es decir, como algo que tiene una dirección almacenada), debe cumplir la "regla de una definición" (ODR), es decir, debe definirse en una y solo una 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. Al definir ese objeto en alguna parte no solo lo está definiendo, en realidad le está diciendo al compilador que desea definirlo aquí , en esta unidad de traducción específica.
Mientras tanto, en lenguaje C ++ las constantes integrales tienen un estado especial. Pueden formar expresiones de constante integrales (ICE). En las ICE, las constantes integrales se utilizan como valores ordinarios, no como objetos (es decir, no es relevante si dicho valor integral tiene una 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 tienen 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.
Si usa Visual C ++, puede hacerlo de forma no portátil con sugerencias para el vinculador ...
// 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 aunque Foo::Bar
será declarado en múltiples archivos de objetos, el enlazador solo seleccionará uno.
Tenga en cuenta que esto solo funcionará con la cadena de herramientas de Microsoft. No esperes que esto sea portátil.
class A{
public:
static const char* SOMETHING() { return "something"; }
};
Lo hago todo el tiempo, especialmente para los costosos parámetros predeterminados.
class A{
static
const expensive_to_construct&
default_expensive_to_construct(){
static const expensive_to_construct xp2c(whatever is needed);
return xp2c;
}
};