constructores estáticos en C ++? Necesito inicializar objetos privados estáticos


176

Quiero tener una clase con un miembro privado de datos estáticos (un vector que contiene todos los caracteres az). En java o C #, simplemente puedo hacer un "constructor estático" que se ejecutará antes de hacer cualquier instancia de la clase, y configurar los miembros de datos estáticos de la clase. Solo se ejecuta una vez (ya que las variables son de solo lectura y solo necesitan configurarse una vez) y dado que es una función de la clase, puede acceder a sus miembros privados. Podría agregar código en el constructor que verifica si el vector se inicializa e inicializarlo si no es así, pero eso introduce muchas verificaciones necesarias y no parece ser la solución óptima para el problema.

Se me ocurre que, dado que las variables serán de solo lectura, solo pueden ser constantes estáticas públicas, por lo que puedo configurarlas una vez fuera de la clase, pero una vez más, parece una especie de truco feo.

¿Es posible tener miembros privados de datos estáticos en una clase si no quiero inicializarlos en el constructor de instancias?



1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 Esta pregunta se centra en ejecutar código para inicializar objetos estáticos privados , no establecer valores constantes de tipos primitivos estáticos privados. Las soluciones son diferentes.
Gordon Gustafson

Ah, creo que tienes razón, retrocediendo.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:


180

Para obtener el equivalente de un constructor estático, debe escribir una clase ordinaria separada para contener los datos estáticos y luego hacer una instancia estática de esa clase ordinaria.

class StaticStuff
{
     std::vector<char> letters_;

public:
     StaticStuff()
     {
         for (char c = 'a'; c <= 'z'; c++)
             letters_.push_back(c);
     }

     // provide some way to get at letters_
};

class Elsewhere
{
    static StaticStuff staticStuff; // constructor runs once, single instance

};

12
¡Gracias! aunque es muy molesto tener que hacer todo eso. Uno de los muchos "errores" que C # y Java aprendieron.
Gordon Gustafson

109
Si. Siempre señalo a la gente que si C ++ no hubiera cometido todos esos "errores", entonces otros lenguajes tendrían que cometerlos. C ++ cubriendo mucho terreno, incluso cometiendo errores, ha sido excelente para los lenguajes que lo siguieron.
quark

11
Solo un pequeño matiz, ya que los constructores entran en juego, nadie garantiza cuándo se ejecuta el constructor para el objeto estático. Un enfoque mucho más seguro y conocido es class Elsewhere {StaticStuff & get_staticStuff () {static StaticStuff staticStuff; // el constructor se ejecuta una vez, cuando alguien lo necesita por primera vez return staticStuff; }}; Me pregunto si los constructores estáticos en C # y Java pueden proporcionar la misma garantía que el código anterior ...
Oleg Zhylin

13
@Oleg: Sí, lo hacen. Las garantías estándar de que los constructores para todas las variables no locales se ejecutan antes de ingresar main. También garantiza que dentro de una unidad de compilación el orden de construcción está bien definido y el mismo orden que la declaración dentro de la unidad de compilación. Lamentablemente, no definen el orden en varias unidades de compilación.
Martin York el

13
Este es en realidad un caso en el que friendtiene mucho sentido para que la clase Elsewherepueda acceder fácilmente a StaticStufflos elementos internos (sin interrumpir la encapsulación de ninguna manera peligrosa, podría agregar).
Konrad Rudolph

81

Bueno puedes tener

class MyClass
{
    public:
        static vector<char> a;

        static class _init
        {
          public:
            _init() { for(char i='a'; i<='z'; i++) a.push_back(i); }
        } _initializer;
};

No olvides (en el .cpp) esto:

vector<char> MyClass::a;
MyClass::_init MyClass::_initializer;

El programa aún se vinculará sin la segunda línea, pero el inicializador no se ejecutará.


+1 (no lo probé) Pero: ¿Cuándo se llama ctor _init._init ()? ¿Antes o después del ctor de MyClass cuando tengo un objeto MyClass estático? Supongo que no puedes decir ...
ur.

2
hola, ¿dónde puedo encontrar más información sobre esta magia "inicializador"?
Karel Bílek

¿No debería ser en MyClass::a.push_back(i)lugar de a.push_back(i)?
Neel Basu

44
@ur .: _initializeres un subobjeto de MyClass. Los subobjetos se inicializan en este orden: subobjetos de clase base virtual, en orden de profundidad primero, de izquierda a derecha (pero solo inicializando cada subobjeto distinto una vez); luego subobjetos de clase base, en profundidad primero, de izquierda a derecha; luego los subobjetos de miembros en orden de declaración. Por lo tanto, es seguro usar la estrategia de EFraim, siempre que el código _initialisersolo se refiera a los miembros declarados antes.
j_random_hacker

2
Esta respuesta es mejor que la aceptada porque el autor mencionó la inicialización indispensable en el segundo clip de código.
Jeff T.

33

Solución C ++ 11

Desde C ++ 11, simplemente puede usar expresiones lambda para inicializar miembros de clase estáticos. Esto incluso funciona si necesita imponer un orden de construcción entre los distintos miembros estáticos, o si tiene miembros estáticos que son const.

Archivo de cabecera:

class MyClass {
    static const vector<char> letters;
    static const size_t letterCount;
};

Archivo fuente:

// Initialize MyClass::letters by using a lambda expression.
const vector<char> MyClass::letters = [] {
    vector<char> letters;
    for (char c = 'a'; c <= 'z'; c++)
        letters.push_back(c);
    return letters;
}();

// The initialization order of static members is defined by the order of
// definition within the source file, so we can access MyClass::letters here.
const size_t MyClass::letterCount = letters.size();

Solución interesante. en este caso si lanzo una excepción, ¿quién puede atraparlo?
rafi wiener

55
El código de inicialización del programa estático nunca debe arrojar excepciones, o el programa se bloqueará. Debe ajustar la lógica del inicializador en un try catchbloque si se pueden generar excepciones.
emkey08

19

En el archivo .h:

class MyClass {
private:
    static int myValue;
};

En el archivo .cpp:

#include "myclass.h"

int MyClass::myValue = 0;

55
Esto funciona bien para miembros estáticos individuales (independientemente del tipo). La deficiencia en comparación con los constructores estáticos es que no puede imponer un orden entre los distintos miembros estáticos. Si necesita hacer eso, vea la respuesta de Earwicker.
quark

Estoy haciendo exactamente eso, pero aún no se compila. Y dice que esta es el área del problema (en el constructor, no el encabezado)
Flotolk

14

Aquí hay otro enfoque similar al de Daniel Earwicker, que también utiliza la sugerencia de clase de amigo de Konrad Rudolph. Aquí usamos una clase de utilidad de amigo privado interno para inicializar los miembros estáticos de su clase principal. Por ejemplo:

Archivo de cabecera:

class ToBeInitialized
{
    // Inner friend utility class to initialize whatever you need

    class Initializer
    {
    public:
        Initializer();
    };

    friend class Initializer;

    // Static member variables of ToBeInitialized class

    static const int numberOfFloats;
    static float *theFloats;

    // Static instance of Initializer
    //   When this is created, its constructor initializes
    //   the ToBeInitialized class' static variables

    static Initializer initializer;
};

Archivo de implementación:

// Normal static scalar initializer
const int ToBeInitialized::numberOfFloats = 17;

// Constructor of Initializer class.
//    Here is where you can initialize any static members
//    of the enclosing ToBeInitialized class since this inner
//    class is a friend of it.

ToBeInitialized::Initializer::Initializer()
{
    ToBeInitialized::theFloats =
        (float *)malloc(ToBeInitialized::numberOfFloats * sizeof(float));

    for (int i = 0; i < ToBeInitialized::numberOfFloats; ++i)
        ToBeInitialized::theFloats[i] = calculateSomeFancyValue(i);
}

Este enfoque tiene la ventaja de ocultar completamente la clase Initializer del mundo exterior, manteniendo todo lo contenido dentro de la clase para inicializar.


+1 Por dar un ejemplo que mantiene la implementación en su propio archivo.
Andrew Larsson

1
Además, debe asegurarse de que ToBeInitialized::Initializer::Initializer()se llama, por lo que debe agregar ToBeInitialized::Initializer ToBeInitialized::initializer;al archivo de implementación. Tomé algunas cosas de su idea y de la idea de EFraim, y funciona exactamente como lo necesito y se ve limpio. Gracias hombre.
Andrew Larsson

11

Test::StaticTest() se llama exactamente una vez durante la inicialización estática global.

La persona que llama solo tiene que agregar una línea a la función que será su constructor estático.

static_constructor<&Test::StaticTest>::c;fuerza la inicialización de cdurante la inicialización estática global.

template<void(*ctor)()>
struct static_constructor
{
    struct constructor { constructor() { ctor(); } };
    static constructor c;
};

template<void(*ctor)()>
typename static_constructor<ctor>::constructor static_constructor<ctor>::c;

/////////////////////////////

struct Test
{
    static int number;

    static void StaticTest()
    {
        static_constructor<&Test::StaticTest>::c;

        number = 123;
        cout << "static ctor" << endl;
    }
};

int Test::number;

int main(int argc, char *argv[])
{
    cout << Test::number << endl;
    return 0;
}

Esta es una solución fantástica. Realmente me gusta la respuesta de Douglas Mandel también, pero esto es aún más conciso.
FlintZA

¡Esto es realmente asombroso!
nh_

9

Sin necesidad de una init()función, std::vectorse puede crear desde un rango:

// h file:
class MyClass {
    static std::vector<char> alphabet;
// ...
};

// cpp file:
#include <boost/range.hpp>
static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
std::vector<char> MyClass::alphabet( boost::begin( ::alphabet ), boost::end( ::alphabet ) );

Sin embargo, tenga en cuenta que las estadísticas de tipo de clase causan problemas en las bibliotecas, por lo que deben evitarse allí.

Actualización de C ++ 11

A partir de C ++ 11, puede hacer esto en su lugar:

// cpp file:
std::vector<char> MyClass::alphabet = { 'a', 'b', 'c', ..., 'z' };

Es semánticamente equivalente a la solución C ++ 98 en la respuesta original, pero no puede usar un literal de cadena en el lado derecho, por lo que no es completamente superior. Sin embargo, si tiene un vector de cualquier otro tipo que char, wchar_t, char16_to char32_t(arrays de los cuales se puede escribir como literales de cadena), la versión C ++ 11 eliminará estrictamente código repetitivo sin introducir otra sintaxis, en comparación con el C ++ 98 versión.


Me gusta. Aunque si pudiéramos hacerlo en una línea sin el alfabeto ahora inútil.
Martin York

Para causar problemas con las bibliotecas, ¿importa si la clase estática es privada o pública? Además, ¿importa si la biblioteca es estática (.a) o dinámica (.so)?
Zachary Kraus

@ZacharyKraus: ¿qué es una clase pública / privada ? Y no, aunque los problemas son diferentes, pero se superponen, no importa si la biblioteca está vinculada de forma estática o dinámica.
Marc Mutz - mmutz

@ MarcMutz-mmutz Perdón por usar la clase pública / privada que no es la terminología correcta de C ++. A lo que me refería es a la solución de EFraim anterior. Sin embargo, en mi versión, hice que el miembro de la clase estática sea privado. Estaba tratando de entender si tener un miembro de clase estático como público o privado marca la diferencia en el desarrollo y la usabilidad de la biblioteca. Mi instinto me dice que no debería afectar la biblioteca porque los usuarios nunca tendrán acceso al miembro de la clase estática o al objeto que está construyendo, pero me encantaría obtener la sabiduría de un gurú sobre este tema.
Zachary Kraus

@ZacharyKraus: El principal problema con las estadísticas que requieren una inicialización dinámica ([basic.start.init] / 2) es que ejecutan código. En las bibliotecas, podría ser que el código de la biblioteca ya se haya descargado cuando se ejecutan los destructores. Si desea saber más, le sugiero que publique una pregunta al respecto.
Marc Mutz - mmutz

6

El concepto de constructores estáticos se introdujo en Java después de que aprendieron de los problemas en C ++. Entonces no tenemos un equivalente directo.

La mejor solución es usar tipos de POD que puedan inicializarse explícitamente.
O haga que sus miembros estáticos sean de un tipo específico que tenga su propio constructor que lo inicialice correctamente.

//header

class A
{
    // Make sure this is private so that nobody can missues the fact that
    // you are overriding std::vector. Just doing it here as a quicky example
    // don't take it as a recomendation for deriving from vector.
    class MyInitedVar: public std::vector<char>
    {
        public:
        MyInitedVar()
        {
           // Pre-Initialize the vector.
           for(char c = 'a';c <= 'z';++c)
           {
               push_back(c);
           }
        }
    };
    static int          count;
    static MyInitedVar  var1;

};


//source
int            A::count = 0;
A::MyInitedVar A::var1;

4

Cuando intento compilar y usar class Elsewhere(de la respuesta de Earwicker ) obtengo:

error LNK2001: unresolved external symbol "private: static class StaticStuff Elsewhere::staticStuff" (?staticStuff@Elsewhere@@0VStaticStuff@@A)

Parece que no es posible inicializar atributos estáticos de tipos no enteros sin poner algún código fuera de la definición de clase (CPP).

Para hacer esa compilación, puede usar " un método estático con una variable local estática dentro ". Algo como esto:

class Elsewhere
{
public:
    static StaticStuff& GetStaticStuff()
    {
        static StaticStuff staticStuff; // constructor runs once, single instance
        return staticStuff;
    }
};

Y también puede pasar argumentos al constructor o inicializarlo con valores específicos, es muy flexible, potente y fácil de implementar ... lo único es que tiene un método estático que contiene una variable estática, no un atributo estático ... La sintaxis cambia un poco, pero sigue siendo útil. Espero que esto sea útil para alguien,

Hugo González Castro.


Aunque tenga cuidado si usa hilos. Creo que en GCC la construcción de locales estáticos está protegida contra la ejecución concurrente, pero en Visual C ++ no lo está.
Daniel Earwicker

1
De C ++ 11 en adelante, y en POSIX, que tiene que ser seguro para subprocesos.
Marc Mutz - mmutz

Me gustaron bastante otras dos soluciones anteriores ( esto y esto ), pero la suya es la única que garantiza la inicialización de las estadísticas en el orden en que se necesitan en todas las bibliotecas. Solo tengo un método de instancia estática privado como el anterior, y envuelvo el acceso a otros valores en accesores estáticos públicos que usan ese método de instancia en lugar de referencias directas. Gracias.
FlintZA

¡Increíble! Esto lo completa.
Gabe Halsmer

4

Supongo que la solución simple a esto será:

    //X.h
    #pragma once
    class X
    {
    public:
            X(void);
            ~X(void);
    private:
            static bool IsInit;
            static bool Init();
    };

    //X.cpp
    #include "X.h"
    #include <iostream>

    X::X(void)
    {
    }


    X::~X(void)
    {
    }

    bool X::IsInit(Init());
    bool X::Init()
    {
            std::cout<< "ddddd";
            return true;
    }

    // main.cpp
    #include "X.h"
    int main ()
    {
            return 0;
    }

Así es como yo también lo hago.
Etherealone

1

Acabo de resolver el mismo truco. Tuve que especificar la definición de un solo miembro estático para Singleton. Pero haga las cosas más complicadas: he decidido que no quiero llamar al ctor de RandClass () a menos que lo use ... es por eso que no quería inicializar singleton globalmente en mi código. También he agregado una interfaz simple en mi caso.

Aquí está el código final:

Simplifiqué el código y utilicé la función rand () y su inicializador de inicio único srand ()

interface IRandClass
{
 public:
    virtual int GetRandom() = 0;
};

class RandClassSingleton
{
private:
  class RandClass : public IRandClass
  {
    public:
      RandClass()
      {
        srand(GetTickCount());
      };

     virtual int GetRandom(){return rand();};
  };

  RandClassSingleton(){};
  RandClassSingleton(const RandClassSingleton&);

  // static RandClass m_Instance;

  // If you declare m_Instance here you need to place
  // definition for this static object somewhere in your cpp code as
  // RandClassSingleton::RandClass RandClassSingleton::m_Instance;

  public:

  static RandClass& GetInstance()
  {
      // Much better to instantiate m_Instance here (inside of static function).
      // Instantiated only if this function is called.

      static RandClass m_Instance;
      return m_Instance;
  };
};

main()
{
    // Late binding. Calling RandClass ctor only now
    IRandClass *p = &RandClassSingleton::GetInstance();
    int randValue = p->GetRandom();
}
abc()
{
    IRandClass *same_p = &RandClassSingleton::GetInstance();
}

1

Aquí está mi variante de la solución de EFraim; la diferencia es que, gracias a la creación de instancias implícita de plantilla, el constructor estático solo se llama si se crean instancias de la clase, y que no .cppse necesita ninguna definición en el archivo (gracias a la magia de creación de instancias de plantilla).

En el .harchivo, tienes:

template <typename Aux> class _MyClass
{
    public:
        static vector<char> a;
        _MyClass() {
            (void) _initializer; //Reference the static member to ensure that it is instantiated and its initializer is called.
        }
    private:
        static struct _init
        {
            _init() { for(char i='a'; i<='z'; i++) a.push_back(i); }
        } _initializer;

};
typedef _MyClass<void> MyClass;

template <typename Aux> vector<char> _MyClass<Aux>::a;
template <typename Aux> typename _MyClass<Aux>::_init _MyClass<Aux>::_initializer;

En el .cpparchivo, puede tener:

void foobar() {
    MyClass foo; // [1]

    for (vector<char>::iterator it = MyClass::a.begin(); it < MyClass::a.end(); ++it) {
        cout << *it;
    }
    cout << endl;
}

Tenga en cuenta que MyClass::asolo se inicializa si la línea [1] está allí, porque eso llama (y requiere la creación de instancias) del constructor, que luego requiere la creación de instancias de _initializer.


1

Aquí hay otro método, donde el vector es privado para el archivo que contiene la implementación mediante el uso de un espacio de nombres anónimo. Es útil para cosas como tablas de búsqueda que son privadas para la implementación:

#include <iostream>
#include <vector>
using namespace std;

namespace {
  vector<int> vec;

  struct I { I() {
    vec.push_back(1);
    vec.push_back(3);
    vec.push_back(5);
  }} i;
}

int main() {

  vector<int>::const_iterator end = vec.end();
  for (vector<int>::const_iterator i = vec.begin();
       i != end; ++i) {
    cout << *i << endl;
  }

  return 0;
}

Aunque es posible que desee nombrar Iy ialgo un poco más oscuro para que no los use accidentalmente en algún lugar más bajo del archivo.
Jim Hunziker

1
Para ser honesto, es difícil ver por qué alguien querría usar miembros estáticos privados en lugar de espacios de nombres anónimos en los archivos de implementación.
Jim Hunziker

1

Ciertamente no necesita ser tan complicado como la respuesta actualmente aceptada (por Daniel Earwicker). La clase es superflua. No hay necesidad de una guerra de idiomas en este caso.

archivo .hpp:

vector<char> const & letters();

archivo .cpp:

vector<char> const & letters()
{
  static vector<char> v = {'a', 'b', 'c', ...};
  return v;
}


0

Defina variables miembro estáticas de forma similar a la forma en que define los métodos miembros.

foo.h

class Foo
{
public:
    void bar();
private:
    static int count;
};

foo.cpp

#include "foo.h"

void Foo::bar()
{
    // method definition
}

int Foo::count = 0;

2
CrazyJugglerDrummer cuestión era no sobre una llanura de edad tipo de datos estática :)
JWW

0

Para inicializar una variable estática, solo debe hacerlo dentro de un archivo fuente. Por ejemplo:

//Foo.h
class Foo
{
 private:
  static int hello;
};


//Foo.cpp
int Foo::hello = 1;

CrazyJugglerDrummer cuestión era no sobre una llanura de edad tipo de datos estática :)
JWW

0

¿Qué tal crear una plantilla para imitar el comportamiento de C #?

template<class T> class StaticConstructor
{
    bool m_StaticsInitialised = false;

public:
    typedef void (*StaticCallback)(void);

    StaticConstructor(StaticCallback callback)
    {
        if (m_StaticsInitialised)
            return;

        callback();

        m_StaticsInitialised = true;
    }
}

template<class T> bool StaticConstructor<T>::m_StaticsInitialised;

class Test : public StaticConstructor<Test>
{
    static std::vector<char> letters_;

    static void _Test()
    {
        for (char c = 'a'; c <= 'z'; c++)
            letters_.push_back(c);
    }

public:
    Test() : StaticConstructor<Test>(&_Test)
    {
        // non static stuff
    };
};

0

Para casos simples como aquí, una variable estática envuelta dentro de una función miembro estática es casi tan buena. Es simple y generalmente será compilado por los compiladores. Sin embargo, esto no resuelve el problema del orden de inicialización para objetos complejos.

#include <iostream>

class MyClass 
{

    static const char * const letters(void){
        static const char * const var = "abcdefghijklmnopqrstuvwxyz";
        return var;
    }

    public:
        void show(){
            std::cout << letters() << "\n";
        }
};


int main(){
    MyClass c;
    c.show();
}

0

¿Es esta una solución?

class Foo
{
public:
    size_t count;
    Foo()
    {
        static size_t count = 0;
        this->count = count += 1;
    }
};

0

Se puede emular un constructor estático utilizando una clase amiga o una clase anidada como se muestra a continuación.

class ClassStatic{
private:
    static char *str;
public:
    char* get_str() { return str; }
    void set_str(char *s) { str = s; }
    // A nested class, which used as static constructor
    static class ClassInit{
    public:
        ClassInit(int size){ 
            // Static constructor definition
            str = new char[size];
            str = "How are you?";
        }
    } initializer;
};

// Static variable creation
char* ClassStatic::str; 
// Static constructor call
ClassStatic::ClassInit ClassStatic::initializer(20);

int main() {
    ClassStatic a;
    ClassStatic b;
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    a.set_str("I am fine");
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    std::cin.ignore();
}

Salida:

String in a: How are you?
String in b: How are you?
String in a: I am fine
String in b: I am fine

¿Por qué está newutilizando una matriz de caracteres solo para filtrar inmediatamente el puntero y sobrescribirlo?
Eric

0

Wow, no puedo creer que nadie mencionó la respuesta más obvia, y una que imita más estrechamente el comportamiento del constructor estático de C #, es decir, no se llama hasta que se crea el primer objeto de ese tipo.

std::call_once()está disponible en C ++ 11; si no puede usar eso, se puede hacer con una variable de clase booleana estática y una operación atómica de comparar e intercambiar. En su constructor, vea si puede cambiar atómicamente el indicador de clase estática de falsea true, y si es así, puede ejecutar el código de construcción estática.

Para obtener crédito adicional, conviértalo en un indicador de 3 vías en lugar de un valor booleano, es decir, no ejecutar, ejecutar y finalizar la ejecución. Luego, todas las demás instancias de esa clase pueden girar y bloquear hasta que la instancia que ejecuta el constructor estático haya finalizado (es decir, emita un límite de memoria, luego establezca el estado en "ejecución finalizada"). Su spin-lock debe ejecutar la instrucción de "pausa" del procesador, duplicar la espera cada vez hasta un umbral, etc. - técnica de bloqueo de spin bastante estándar.

En ausencia de C ++ 11, esto debería ayudarlo a comenzar.

Aquí hay un pseudocódigo para guiarte. Pon esto en la definición de tu clase:

enum EStaticConstructor { kNotRun, kRunning, kDone };
static volatile EStaticConstructor sm_eClass = kNotRun;

Y esto en tu constructor:

while (sm_eClass == kNotRun)
{
    if (atomic_compare_exchange_weak(&sm_eClass, kNotRun, kRunning))
    {
        /* Perform static initialization here. */

        atomic_thread_fence(memory_order_release);
        sm_eClass = kDone;
    }
}
while (sm_eClass != kDone)
    atomic_pause();
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.