¿Cómo inicializar miembros estáticos privados en C ++?

521

¿Cuál es la mejor manera de inicializar un miembro de datos estático privado en C ++? Intenté esto en mi archivo de encabezado, pero me da errores raros de enlazador:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

Supongo que esto se debe a que no puedo inicializar un miembro privado desde fuera de la clase. Entonces, ¿cuál es la mejor manera de hacer esto?

Jason Baker
fuente
2
Hola Jason. No encontré ningún comentario sobre la inicialización predeterminada de los miembros estáticos (especialmente los integrales). De hecho, debe escribir int foo :: i para que el vinculador pueda encontrarlo, ¡pero se inicializará automáticamente con 0! Esta línea sería suficiente: int foo :: i; (Esto es válido para todos los objetos almacenados en la memoria estática, el enlazador se encarga de inicializar los objetos estáticos.)
Nico
1
Las respuestas a continuación no se aplican a una clase de plantilla. Dicen: la inicialización debe ir al archivo fuente. Para una clase de plantilla, esto no es posible ni necesario.
Joachim W
77
C ++ 17 permite la inicialización en línea de miembros de datos estáticos (incluso para los tipos no enteros): inline static int x[] = {1, 2, 3};. Ver en.cppreference.com/w/cpp/language/static#Static_data_members
Vladimir Reshetnikov

Respuestas:

557

La declaración de clase debe estar en el archivo de encabezado (o en el archivo de origen si no se comparte).
Archivo: foo.h

class foo
{
    private:
        static int i;
};

Pero la inicialización debe estar en el archivo fuente.
Archivo: foo.cpp

int foo::i = 0;

Si la inicialización está en el archivo de encabezado, entonces cada archivo que incluye el archivo de encabezado tendrá una definición del miembro estático. Por lo tanto, durante la fase de enlace, obtendrá errores de enlace ya que el código para inicializar la variable se definirá en múltiples archivos de origen. La inicialización de la static int idebe hacerse fuera de cualquier función.

Nota: Matt Curtis: señala que C ++ permite la simplificación de lo anterior si la variable miembro estática es de tipo int constante (p int. Ej . bool, char). Luego puede declarar e inicializar la variable miembro directamente dentro de la declaración de clase en el archivo de encabezado:

class foo
{
    private:
        static int const i = 42;
};
Martin York
fuente
44
Si. Pero supongo que la pregunta se ha simplificado. Técnicamente, la declaración y la definición pueden estar en un solo archivo fuente. Pero eso limita el uso de la clase por otras clases.
Martin York
11
en realidad no es solo POD, también tiene que ser de tipo int (int, short, bool, char ...)
Matt Curtis
99
Tenga en cuenta que esta no es solo una cuestión de cómo se inicializa el valor: la implementación puede convertir los tipos integrales const definidos de esta manera en constantes de tiempo de compilación. Esto no siempre es lo que desea, ya que aumenta la dependencia binaria: el código del cliente necesita recompilación si el valor cambia.
Steve Jessop
55
@ Martin: además de la corrección s / POD / integral type /, si alguna vez se toma la dirección, entonces también debe haber una definición. Por extraño que parezca, la declaración con inicializador, en la definición de clase, no es una definición. El modulo const con plantilla proporciona una solución para los casos en los que necesita la definición en un archivo de encabezado. Otra solución alternativa más simple es una función que produce el valor de una constante estática local. Saludos y hth.,
Saludos y hth. - Alf
3
Puede agregar una aclaración que int foo :: i = 0; no debe estar dentro de una función (incluida la función principal). Lo tenía al comienzo de mi función principal y no me gusta.
qwerty9967
89

Para una variable :

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Esto se debe a que solo puede haber una instancia foo::ien su programa. Es una especie de equivalente extern int ien un archivo de encabezado y int ien un archivo fuente.

Para una constante , puede poner el valor directamente en la declaración de clase:

class foo
{
private:
    static int i;
    const static int a = 42;
};
Matt Curtis
fuente
2
Este es un punto válido. Agregaré esto también a mi explicación. Pero debe tenerse en cuenta que esto solo funciona para los tipos de POD.
Martin York
Desde entonces, C ++ permite ser simplemente bueno con declaración en clase y sin definición para tipos integrales. ¿Desde C ++ 98 o C ++ 03 o cuándo? Por favor comparte enlaces auténticos por favor. La redacción estándar de C ++ no está sincronizada con los compiladores. Mencionan que el miembro aún se definirá si se usan. Sin embargo
citas
1
Me pregunto por qué las privatevariables se pueden inicializar fuera de la clase aquí, ¿se puede hacer esto también para variables no estáticas?
Krishna Oza
¿Has encontrado la explicación? @Krishna_Oza
nn0p
@ nn0p todavía no, pero la inicialización de variables privadas no estáticas en el exterior Classno tiene ningún sentido en Cpp.
Krishna Oza
42

Desde C ++ 17, los miembros estáticos pueden definirse en el encabezado con la palabra clave en línea .

http://en.cppreference.com/w/cpp/language/static

"Un miembro de datos estáticos puede declararse en línea. Un miembro de datos estáticos en línea puede definirse en la definición de clase y puede especificar un inicializador de miembro predeterminado. No necesita una definición fuera de clase:"

struct X
{
    inline static int n = 1;
};
Muere en Sente
fuente
1
Esto es posible desde C ++ 17, que actualmente está en proceso de convertirse en el nuevo estándar.
Grebu
31

Para los futuros espectadores de esta pregunta, quiero señalar que deben evitar lo que monkey0506 sugiere .

Los archivos de encabezado son para declaraciones.

Los archivos de encabezado se compilan una vez por cada .cpparchivo que los directa o indirectamente #includes, y el código fuera de cualquier función se ejecuta en la inicialización del programa, antesmain() .

Al poner: foo::i = VALUE;en el encabezado, foo:ise le asignará el valor VALUE(lo que sea que sea) para cada .cpparchivo, y estas asignaciones sucederán en un orden indeterminado (determinado por el vinculador) antes de main()ejecutarse.

¿Qué pasa si somos #define VALUEun número diferente en uno de nuestros .cpparchivos? Se compilará bien y no tendremos forma de saber cuál gana hasta que ejecutemos el programa.

Nunca coloque el código ejecutado en un encabezado por la misma razón que nunca #includeun .cpparchivo.

incluir guardias (que estoy de acuerdo en que siempre debe usar) lo protegen de algo diferente: el mismo encabezado se usa indirectamente #includevarias veces al compilar un solo .cpparchivo

Joshua Clayton
fuente
2
Tiene razón sobre esto, por supuesto, excepto en el caso de una plantilla de clase (que no se pregunta, pero estoy lidiando con mucho). Entonces, si la clase está completamente definida y no es una plantilla de clase, coloque estos miembros estáticos en un archivo CPP separado, pero para las plantillas de clase la definición debe estar en la misma unidad de traducción (por ejemplo, el archivo de encabezado).
monkey0506
@ monkey_05_06: Eso parece ser un argumento para evitar un miembro estático en el código de plantilla: ya terminas con un miembro estático para cada instanciación de la clase. el problema empeora compilando posiblemente el encabezado en múltiples archivos cpp ... Podría obtener una gran cantidad de definiciones conflictivas.
Joshua Clayton
publib.boulder.ibm.com/infocenter/macxhelp/v6v81/… Este enlace muestra la creación de instancias de miembros de plantillas estáticas en la función principal, que es más limpia, si es un poco pesada.
Joshua Clayton
1
Su argumento es realmente enorme. Primero no puede #definir VALOR porque el nombre de las macros no debe ser un identificador válido. E incluso si pudieras, ¿quién haría eso? Los archivos de encabezado son para declaración -? Vamos ... Los únicos casos en los que debes evitar poner valores en el encabezado es luchar contra odr-used. Y poner el valor en el encabezado puede llevar a una compilación innecesaria cada vez que necesite cambiar el valor.
Aleksander Fular
20

Con un compilador de Microsoft [1], las variables estáticas que no son intsimilares también se pueden definir en un archivo de encabezado, pero fuera de la declaración de clase, utilizando el específico de Microsoft __declspec(selectany).

class A
{
    static B b;
}

__declspec(selectany) A::b;

Tenga en cuenta que no digo que esto sea bueno, solo digo que se puede hacer.

[1] En estos días, hay más compiladores que el soporte de MSC __declspec(selectany), al menos gcc y clang. Quizás aún más.

Johann Gerell
fuente
17
int foo::i = 0; 

Es la sintaxis correcta para inicializar la variable, pero debe ir en el archivo fuente (.cpp) en lugar de en el encabezado.

Debido a que es una variable estática, el compilador necesita crear solo una copia de ella. Debe tener una línea "int foo: i" en algún lugar de su código para indicarle al compilador dónde colocarlo; de lo contrario, obtendrá un error de enlace. Si está en un encabezado, obtendrá una copia en cada archivo que incluya el encabezado, por lo tanto, obtenga múltiples errores de símbolos definidos desde el enlazador.

David Dibben
fuente
12

No tengo suficiente representante aquí para agregar esto como un comentario, pero en mi opinión, es un buen estilo escribir sus encabezados con #include guardias de todos modos, lo que, como señaló Paranaix hace unas horas, evitaría un error de definición múltiple. A menos que ya esté usando un archivo CPP separado, no es necesario usar uno solo para inicializar miembros no integrales estáticos.

#ifndef FOO_H
#define FOO_H
#include "bar.h"

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

No veo la necesidad de usar un archivo CPP separado para esto. Claro que puedes, pero no hay una razón técnica por la que debas hacerlo.

monkey0506
fuente
21
# incluir guardias solo evitan múltiples definiciones por unidad de traducción.
Paul Fultz II
3
con respecto al buen estilo: debe agregar un comentario sobre el final de cierre:#endif // FOO_H
Riga
99
Esto solo funciona si solo tiene una unidad de compilación que incluye foo.h. Si dos o más cpps incluyen foo.h, que es una situación típica, cada cpp declararía la misma variable estática, por lo que el enlazador se quejaría con la definición múltiple de 'foo :: i' a menos que use una compilación de paquete con los archivos (compilación solo un archivo que incluye todos los cpps). Pero aunque la compilación de paquetes es excelente, la solución al problema es declarar (int foo :: i = 0;) en un cpp.
Alejadro Xalabarder
1
O simplemente use#pragma once
tambre
12

Si desea inicializar algún tipo compuesto (cadena fe), puede hacer algo así:

class SomeClass {
  static std::list<string> _list;

  public:
    static const std::list<string>& getList() {
      struct Initializer {
         Initializer() {
           // Here you may want to put mutex
           _list.push_back("FIRST");
           _list.push_back("SECOND");
           ....
         }
      }
      static Initializer ListInitializationGuard;
      return _list;
    }
};

Como ListInitializationGuardes un SomeClass::getList()método interno de variable estática , se construirá solo una vez, lo que significa que se llama al constructor una vez. Esto variará initialize _listal valor que necesita. Cualquier llamada posterior a getListsimplemente devolverá el _listobjeto ya inicializado .

Por supuesto, debe acceder al _listobjeto siempre llamando al getList()método.

Kris Kwiatkowski
fuente
1
Aquí hay una versión de este idioma que no requiere la creación de un método por objeto miembro: stackoverflow.com/a/48337288/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
9

Patrón de constructor estático C ++ 11 que funciona para múltiples objetos

Se propuso un modismo en: https://stackoverflow.com/a/27088552/895245, pero aquí hay una versión más limpia que no requiere la creación de un nuevo método por miembro.

main.cpp

#include <cassert>
#include <vector>

// Normally on the .hpp file.
class MyClass {
public:
    static std::vector<int> v, v2;
    static struct StaticConstructor {
        StaticConstructor() {
            v.push_back(1);
            v.push_back(2);
            v2.push_back(3);
            v2.push_back(4);
        }
    } _staticConstructor;
};

// Normally on the .cpp file.
std::vector<int> MyClass::v;
std::vector<int> MyClass::v2;
// Must come after every static member.
MyClass::StaticConstructor MyClass::_staticConstructor;

int main() {
    assert(MyClass::v[0] == 1);
    assert(MyClass::v[1] == 2);
    assert(MyClass::v2[0] == 3);
    assert(MyClass::v2[1] == 4);
}

GitHub aguas arriba .

Compilar y ejecutar:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

Ver también: constructores estáticos en C ++? Necesito inicializar objetos privados estáticos

Probado en Ubuntu 19.04.

C ++ 17 variable en línea

Mencionado en: https://stackoverflow.com/a/45062055/895245 pero aquí hay un ejemplo ejecutable de múltiples archivos para hacerlo aún más claro: ¿Cómo funcionan las variables en línea?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
5

También puede incluir la asignación en el archivo de encabezado si usa protectores de encabezado. He usado esta técnica para una biblioteca C ++ que he creado. Otra forma de lograr el mismo resultado es usar métodos estáticos. Por ejemplo...

class Foo
   {
   public:
     int GetMyStatic() const
     {
       return *MyStatic();
     }

   private:
     static int* MyStatic()
     {
       static int mStatic = 0;
       return &mStatic;
     }
   }

El código anterior tiene la "ventaja" de no requerir un archivo CPP / fuente. De nuevo, un método que uso para mis bibliotecas C ++.


fuente
4

Sigo la idea de Karl. Me gusta y ahora también lo uso. He cambiado un poco la notación y agregué algunas funcionalidades

#include <stdio.h>

class Foo
{
   public:

     int   GetMyStaticValue () const {  return MyStatic();  }
     int & GetMyStaticVar ()         {  return MyStatic();  }
     static bool isMyStatic (int & num) {  return & num == & MyStatic(); }

   private:

      static int & MyStatic ()
      {
         static int mStatic = 7;
         return mStatic;
      }
};

int main (int, char **)
{
   Foo obj;

   printf ("mystatic value %d\n", obj.GetMyStaticValue());
   obj.GetMyStaticVar () = 3;
   printf ("mystatic value %d\n", obj.GetMyStaticValue());

   int valMyS = obj.GetMyStaticVar ();
   int & iPtr1 = obj.GetMyStaticVar ();
   int & iPtr2 = valMyS;

   printf ("is my static %d %d\n", Foo::isMyStatic(iPtr1), Foo::isMyStatic(iPtr2));
}

esto produce

mystatic value 7
mystatic value 3
is my static 1 0
Alejadro Xalabarder
fuente
3

También trabajando en el archivo privateStatic.cpp:

#include <iostream>

using namespace std;

class A
{
private:
  static int v;
};

int A::v = 10; // possible initializing

int main()
{
A a;
//cout << A::v << endl; // no access because of private scope
return 0;
}

// g++ privateStatic.cpp -o privateStatic && ./privateStatic
Andrés
fuente
3

¿Qué hay de un set_default()método?

class foo
{
    public:
        static void set_default(int);
    private:
        static int i;
};

void foo::set_default(int x) {
    i = x;
}

Solo tendríamos que usar el set_default(int x)método y nuestra staticvariable se inicializaría.

Esto no estaría en desacuerdo con el resto de los comentarios, en realidad sigue el mismo principio de inicializar la variable en un ámbito global, pero al usar este método lo hacemos explícito (y fácil de ver y entender) en lugar de tener la definición de la variable colgando allí.

Arturo Ruiz Mañas
fuente
3

El problema del enlazador que encontró probablemente sea causado por:

  • Proporcionando definición de miembro tanto de clase como estático en el archivo de encabezado,
  • Incluyendo este encabezado en dos o más archivos de origen.

Este es un problema común para aquellos que comienzan con C ++. El miembro de clase estático debe inicializarse en una sola unidad de traducción, es decir, en un único archivo fuente.

Desafortunadamente, el miembro de la clase estática debe inicializarse fuera del cuerpo de la clase. Esto complica escribir código de solo encabezado y, por lo tanto, estoy usando un enfoque bastante diferente. Puede proporcionar su objeto estático a través de una función de clase estática o no estática, por ejemplo:

class Foo
{
    // int& getObjectInstance() const {
    static int& getObjectInstance() {
        static int object;
        return object;
    }

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};
nadie especial
fuente
1
Todavía soy un completo n00b en lo que respecta a C ++, pero esto me parece genial, ¡muchas gracias! Obtengo una gestión perfecta del ciclo de vida del objeto singleton de forma gratuita.
Rafael Kitover
2

Una forma "antigua" de definir constantes es reemplazarlas por enum:

class foo
{
    private:
        enum {i = 0}; // default type = int
        enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

De esta manera, no es necesario proporcionar una definición y evita hacer el valor constante l , lo que puede ahorrarle algunos dolores de cabeza, por ejemplo, cuando lo usa ODR accidentalmente .

anatolyg
fuente
1

Solo quería mencionar algo un poco extraño para mí cuando me encontré con esto por primera vez.

Necesitaba inicializar un miembro privado de datos estáticos en una clase de plantilla.

en .h o .hpp, se ve algo así para inicializar un miembro de datos estáticos de una clase de plantilla:

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;
Tyler Heers
fuente
0

¿Esto sirve a su propósito?

//header file

struct MyStruct {
public:
    const std::unordered_map<std::string, uint32_t> str_to_int{
        { "a", 1 },
        { "b", 2 },
        ...
        { "z", 26 }
    };
    const std::unordered_map<int , std::string> int_to_str{
        { 1, "a" },
        { 2, "b" },
        ...
        { 26, "z" }
    };
    std::string some_string = "justanotherstring";  
    uint32_t some_int = 42;

    static MyStruct & Singleton() {
        static MyStruct instance;
        return instance;
    }
private:
    MyStruct() {};
};

//Usage in cpp file
int main(){
    std::cout<<MyStruct::Singleton().some_string<<std::endl;
    std::cout<<MyStruct::Singleton().some_int<<std::endl;
    return 0;
}
David Nogueira
fuente