c++ - estaticos - Cómo inicializar miembros estáticos en el encabezado
miembros estaticos de una clase java (7)
Dado es una clase con un miembro estático.
class BaseClass
{
public:
static std::string bstring;
};
La cadena obviamente tiene que estar inicializada por defecto fuera de la clase.
std::string BaseClass::bstring {"."};
Si incluyo la línea anterior en el encabezado junto con la clase, obtengo un symbol multiply defined
error symbol multiply defined
. Debe definirse en un archivo cpp
separado, incluso con include guards
o pragma once
.
¿No hay una manera de definirlo en el encabezado?
ACTUALIZACIÓN: Mi respuesta a continuación explica por qué esto no puede hacerse de la manera sugerida por la pregunta. Hay al menos dos respuestas que lo eluden; Pueden o no resolver el problema.
El miembro estático bstring
tiene que estar vinculado a una dirección de memoria específica. Para que esto suceda, tiene que aparecer en un solo archivo de objeto, por lo tanto, tiene que aparecer en un solo archivo cpp
. A menos que esté jugando con #ifdef
''s para asegurarse de que esto suceda, lo que desea no se puede hacer en el archivo de encabezado, ya que su archivo de encabezado puede estar incluido por más de un archivo cpp
.
No puedes definir una variable miembro static
más de una vez. Si coloca definiciones de variables en un encabezado, se definirá en cada unidad de traducción donde se incluye el encabezado. Como los guardias de inclusión solo afectan la compilación de una unidad de traducción, tampoco ayudarán.
Sin embargo, puede definir funciones miembro static
! Ahora, a primera vista, puede que no parezca que pueda ayudar, excepto que, por supuesto, esa función puede tener una variable static
local y devolver una referencia a uno de estos se comporta casi como una variable miembro static
:
static std::string& bstring() { static std::string rc{"."}; return rc; }
La variable static
local se inicializará la primera vez que se llame a esta función. Es decir, la construcción se retrasa hasta que se accede a la función la primera vez. Por supuesto, si usa esta función para inicializar otros objetos globales, también puede asegurarse de que el objeto se construya a tiempo. Si usa varios subprocesos, esto puede parecer una posible carrera de datos, pero no lo es (a menos que use C ++ 03): la inicialización de la función de static
variable static
local es segura para subprocesos.
No, no se puede hacer en un encabezado, al menos no si el encabezado se incluye más de una vez en los archivos de origen, lo que parece ser el caso, o si no obtendría un error como ese. Solo pégalo en uno de los archivos .cpp y termina con él.
Para mantener la definición de un valor estático con la declaración en C ++ 11, se puede usar una estructura estática anidada. En este caso, el miembro estático es una estructura y debe definirse en un archivo .cpp, pero los valores están en el encabezado.
class BaseClass
{
public:
static struct _Static {
std::string bstring {"."};
} global;
};
En lugar de inicializar miembros individuales, se inicializa toda la estructura estática:
BaseClass::_Static BaseClass::global;
Se accede a los valores con
BaseClass::global.bstring;
Tenga en cuenta que esta solución aún sufre el problema del orden de inicialización de las variables estáticas. Cuando se usa un valor estático para inicializar otra variable estática, es posible que el primero aún no se haya inicializado.
// file.h
class File {
public:
static struct _Extensions {
const std::string h{ ".h" };
const std::string hpp{ ".hpp" };
const std::string c{ ".c" };
const std::string cpp{ ".cpp" };
} extension;
};
// file.cpp
File::_Extensions File::extension;
// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };
En este caso, los encabezados de variables estáticas contendrán {""} o {".h", ".hpp"}, según el orden de inicialización creado por el enlazador.
Respecto a
" ¿No hay una manera de definir [el miembro de datos estáticos] en el encabezado?
Sí hay.
template< class Dummy >
struct BaseClass_statics
{
static std::string bstring;
};
template< class Dummy >
std::string BaseClass_statics<Dummy>::bstring = ".";
class BaseClass
: public BaseClass_statics<void>
{};
Una alternativa es usar una función, como sugirió Dietmar. Esencialmente eso es un singleton de Meyers (google).
Edit : Además, desde que se publicó esta respuesta, tenemos la propuesta de objeto en línea, que creo que se acepta para C ++ 17.
De todos modos, piense dos veces sobre el diseño aquí. Las variables globales son Evil ™. Esto es esencialmente un global.
Si el inicializador se puede expresar como un literal, se resuelve en C ++ 11:
inline std::string operator"" _s(const char* p, size_t n) {
return std::string{p, n};
}
class BaseClass {
// inline initialization using user-defined literals
// should allow for multiple definitions
std::string bstring{"."_s};
};
§3.2.6
y los siguientes párrafos del borrador actual de c ++ 17 (n4296) definen las reglas cuando más de una definición puede estar presente en diferentes unidades de traducción:
Puede haber más de una definición de un tipo de clase (Cláusula 9), tipo de enumeración (7.2), función en línea con enlace externo (7.1.2), plantilla de clase (Cláusula 14), plantilla de función no estática (14.5.6) , miembro de datos estáticos de una plantilla de clase (14.5.1.3), función miembro de una plantilla de clase (14.5.1.1), o especialización de plantilla para la cual no se especifican algunos parámetros de plantilla (14.7, 14.5.5) en un programa siempre que cada uno la definición aparece en una unidad de traducción diferente, y siempre que las definiciones cumplan con los siguientes requisitos. Dada una entidad llamada D definida en más de una unidad de traducción, entonces [...]
Obviamente, no se considera que las definiciones de miembros de datos estáticos de tipo de clase aparezcan en múltiples unidades de traducción. Por lo tanto, de acuerdo con la norma, no está permitido .
Las respuestas sugeridas de Cheers y hth. - Alf y Dietmar son más como un "hack", explotando las definiciones de
miembro de datos estáticos de una plantilla de clase (14.5.1.3)
y
Función en línea con enlace externo (7.1.2)
se permiten en múltiples TU (FYI: las funciones estáticas definidas dentro de una definición de clase tienen enlace externo y se definen implícitamente como en línea).