Pregunta

¿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?

¿Fue útil?

Solución

La declaración de clase debe estar en el archivo de encabezado (O en el archivo fuente 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, cada archivo que incluya el archivo de encabezado tendrá una definición del miembro estático. Por lo tanto, durante la fase de enlace, obtendrá errores de enlazador ya que el código para inicializar la variable se definirá en múltiples archivos fuente.

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 (por ejemplo, int , 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;
};

Otros consejos

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 de foo :: i en su programa. Es una especie de equivalente de extern int i en un archivo de encabezado y int i en 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;
};

Para 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 archivo .cpp que directa o indirectamente los #include , y el código fuera de cualquier función se ejecuta en la inicialización del programa, antes de main () .

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

¿Qué sucede si #define VALUE es 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 la misma razón por la que nunca #include un archivo .cpp .

incluye guardias (que acepto que siempre debes usar) te protegen de algo diferente: el mismo encabezado indirectamente #include d varias veces al compilar un solo .cpp archivo

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

Con un compilador de Microsoft [1], las variables estáticas que no son int también se pueden definir en un archivo de encabezado, pero fuera de la declaración de clase, utilizando el __declspec específico de Microsoft (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, más compiladores que MSC admiten __declspec (selectany) , al menos gcc y clang. Quizás incluso más.

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. Tienes que tener una línea " int foo: i " en algún lugar de su código para decirle 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.

No tengo suficiente representante aquí para agregar esto como 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 puede, pero no hay ninguna razón técnica por la que deba hacerlo.

Si desea inicializar algún tipo compuesto (por ejemplo, cadena) 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 ListInitializationGuard es una variable estática dentro del método SomeClass :: getList () , se construirá solo una vez, lo que significa que se llama al constructor una vez. Esto inicializará _list variable al valor que necesita. Cualquier llamada posterior a getList simplemente devolverá el objeto _list ya inicializado.

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

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 el "bonus" de no requerir un archivo CPP / fuente. De nuevo, un método que uso para mis bibliotecas C ++.

Sigo la idea de Karl. Me gusta y ahora también lo uso. Cambié 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 genera

mystatic value 7
mystatic value 3
is my static 1 0

Patrón de constructor estático que funciona para varios objetos

Se propuso un idioma en: https://stackoverflow.com/a/27088552/895245 pero aquí va un versión más limpia que no requiere crear 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);
}

GitHub upp. .

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

Probado con g ++ -std = c ++ 11 -Wall -Wextra , GCC 7.3, Ubuntu 18.04.

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

¿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 método 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 teniendo la definición de la variable colgando allí.

El problema del vinculador que encontró probablemente es 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 fuente.

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

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;

Una "vieja escuela" La forma de definir constantes es reemplazarlas 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 la constante lvalue , lo que puede ahorrarle dolores de cabeza , p.ej cuando accidentalmente ODR-use él.

¿Sirve esto para 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;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top