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




arrays de (15)

¿Cuál es la mejor manera de inicializar un miembro de datos estáticos privados en C ++? Intenté esto en mi archivo de encabezado, pero me da errores extraños del enlazador:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

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


Answers

También puede incluir la asignación en el archivo de encabezado si utiliza protectores de encabezado. He utilizado esta técnica para una biblioteca de 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 el "bono" de no requerir un archivo fuente / CPP. De nuevo, un método que utilizo para mis bibliotecas de C ++.


¿Qué pasa con un método set_default() ?

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) y nuestra variable static 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 que cuelga allí.


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

Los archivos de encabezado son para declaraciones.

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

Poniendo: foo::i = VALUE; en el encabezado, foo:i asignará el valor VALUE (cualquiera que sea) para cada archivo .cpp , y estas asignaciones sucederán en un orden indeterminado (determinado por el vinculador) antes de ejecutar main() .

¿Qué .cpp si #define VALUE para ser un número diferente en uno de nuestros archivos .cpp ? 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 el mismo motivo por el que nunca .cpp un archivo .cpp .

incluir guardias (que estoy de acuerdo con que siempre deberías usar) te protege de algo diferente: el mismo encabezado es indirectamente #include d varias veces al compilar un solo archivo .cpp


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

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

en .h o .hpp, parece algo como esto para inicializar un miembro de datos estáticos de una clase de plantilla:

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;

Una forma "de la vieja escuela" de definir constantes es reemplazándolas por una 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 que el valor de lvalue constante, lo que puede ahorrarle algunos dolores de cabeza, por ejemplo, cuando lo ODR-use accidentalmente con ODR-use .


Sigo la idea de Karl. Me gusta y ahora lo uso también. He cambiado un poco la notación y he añadido 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

El problema del enlazador que encontraste es probablemente causado por:

  • Proporcionar tanto la clase como la definición de miembro estática en el archivo de cabecera,
  • 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 la clase estática debe inicializarse en una sola unidad de traducción, es decir, en un solo archivo fuente.

Desafortunadamente, el miembro de la clase estática debe inicializarse fuera del cuerpo de la clase. Esto complica la escritura de código de solo encabezado y, por lo tanto, estoy usando un enfoque muy diferente. Puede proporcionar su objeto estático a través de la 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;
    }
};

¿Esto sirve para tu 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;
}

int foo::i = 0; 

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

Debido a que es una variable estática, el compilador necesita crear una sola copia. Debe tener una línea "int foo: i" en algún lugar de su código para indicar 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, así que obtenga múltiples errores de símbolos definidos del vinculador.


Desde C ++ 17, los miembros estáticos se pueden definir 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 ser declarado en línea. Un miembro de datos estáticos en línea se puede definir 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;
};

Si quieres inicializar algún tipo compuesto (cadena de fe) puedes hacer algo como eso:

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 ListInitializationGuard es una variable estática dentro del SomeClass::getList() , se construirá solo una vez, lo que significa que se llama al constructor una vez. Esto initialize _list variable initialize _list al valor que necesita. Cualquier llamada posterior a getList simplemente devolverá el objeto _list ya inicializado.

Por supuesto, siempre debe acceder _list objeto getList() llamando getList() método getList() .


Patrón constructor estático que funciona para múltiples objetos.

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

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

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

Probado con g++ -std=c++11 -Wall -Wextra , GCC 7.2, Ubuntu 17.10.


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

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

class A
{
    static B b;
}

__declspec(selectany) A::b;

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

[1] En estos días, más compiladores que MSC admiten __declspec(selectany) - al menos gcc y clang. Tal vez incluso más.


El problema es que las definiciones que das para las variables de miembros estáticos también son plantillas.

template<class T>
std::string B<T>::mB;
template<class T>
typename B<T>::InitHelper B<T>::mInit;

Durante la compilación, esto realmente no define nada, ya que T no se conoce. Es algo así como una declaración de clase o una definición de plantilla, el compilador no genera código ni reserva almacenamiento cuando lo ve.

La definición ocurre implícitamente más tarde, cuando usa la clase de plantilla. Porque en el caso segfaulting no utiliza B <int> :: mInit, nunca se crea.

Una solución sería definir explícitamente el miembro necesario (sin inicializarlo): poner un archivo fuente en alguna parte

template<>
typename B<int>::InitHelper B<int>::mInit;

Esto funciona básicamente de la misma manera que la definición explícita de una clase de plantilla.





c++ initialization static-members