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


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

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

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

Author: peterh, 2009-07-29

21 answers

Para obtener el equivalente de un constructor estático, necesita 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

};
 167
Author: Daniel Earwicker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-29 10:59:28

Bien 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 se olvide (en el .cpp) esto:

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

El programa seguirá enlazando sin la segunda línea, pero el inicializador no se ejecutará.

 72
Author: EFraim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-12 13:43:30

En el .archivo h:

class MyClass {
private:
    static int myValue;
};

En el .archivo cpp:

#include "myclass.h"

int MyClass::myValue = 0;
 19
Author: Ant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-28 22:29:43

Aquí hay otro enfoque similar al de Daniel Earwicker, también usando la sugerencia de clase 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 Inicializer del mundo exterior, manteniendo todo lo contenido dentro de la clase a inicializar.

 15
Author: Douglas Mandell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-20 00:10:35

Inicialización de miembros estáticos (desde C++11)

Desde C++11, la inicialización de miembros estáticos se puede implementar simplemente con funciones lambda.

Archivo de cabecera:

class MyClass {
    static std::vector<char> letters;
};

Archivo fuente:

std::vector<char> MyClass::letters = [] {
    std::vector<char> letters;
    for (char c = 'a'; c <= 'z'; c++)
        letters.push_back(c);
    return letters;
}();

Constructor de clases estáticas (desde C++11)

Desde C++11, los constructores de clases estáticas (inicializando múltiples miembros estáticos diferentes) también se pueden implementar con funciones lambda.

Archivo de cabecera:

class MyClass {
    static Foo staticMember1;
    static Foo staticMember2;
    static const bool StaticCtor;    // Note: must be the last static member
};

Archivo fuente:

Foo MyClass::staticMember1;
Foo MyClass::staticMember2;

const bool MyClass::StaticCtor = [] {
    staticMember1 = ... ;
    staticMember2 = ... ;
    return true;             // Note: unused dummy return value
}();
 12
Author: emkey08,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-10 11:23:04

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

El llamante solo tiene que añadir una línea a la función que va a ser su constructor estático.

static_constructor<&Test::StaticTest>::c; fuerza la inicialización de c durante 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;
}
 10
Author: bitwise,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-25 22:05:45

No hay necesidad de una función init(), std::vector se puede crear a partir de 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 ) );

Tenga en cuenta, sin embargo, que las estáticas de tipo clase causan problemas en las bibliotecas, por lo que deben evitarse allí.

Actualización de C++11

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

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

Es semánticamente equivalente a la solución de 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 usted tiene un vector de cualquier otro tipo que no sea char, wchar_t, char16_t o char32_t (cuyas matrices se pueden escribir como literales de cadena), la versión de C++11 eliminará estrictamente el código repetitivo sin introducir otra sintaxis, en comparación con la versión de C++98.

 9
Author: Marc Mutz - mmutz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-10 21:54:40

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

La mejor solución es usar tipos de POD que puedan inicializarse explícitamente.
O haz que tus 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;
 6
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-28 22:48:32

Al intentar 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 que la compilación se puede utilizar "un método estático con una variable local estática dentro" en su lugar. 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 tienes 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.

 4
Author: Community,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:32

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;
    }
 4
Author: Shubham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-21 05:56:55

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

Aquí está el código final:

Simplifiqué el código y usé la función rand() y su inicializador de semilla simple 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
Author: adspx5,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-20 21:43:38

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

En el archivo .h, 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 archivo .cpp, 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::a se inicializa solo si la línea [1] está allí, porque llama (y requiere instanciación of) el constructor, que luego requiere la instanciación de _initializer.

 1
Author: Blaisorblade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-29 11:31:48

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;
}
 1
Author: Jim Hunziker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-31 16:20:40

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

.archivo hpp:

vector<char> const & letters();

.archivo cpp:

vector<char> const & letters()
{
  static vector<char> v = {'a', 'b', 'c', ...};
  return v;
}
 1
Author: AndyJost,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-05 00:55:35

Se definen variables de miembro estáticas de manera similar a la forma en que se definen los métodos de miembro.

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;
 0
Author: Nick Lewis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-28 22:30:21

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

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


//Foo.cpp
int Foo::hello = 1;
 0
Author: Cristián Romo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-28 22:30:44

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
Author: karmasponge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-29 08:00:43

Para casos simples como aquí, una variable estática envuelta dentro de una función miembro estática es casi igual de buena. Es simple y generalmente será optimizado 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
Author: kriss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-20 00:39:50

¿Es esta una solución?

class Foo
{
public:
    size_t count;
    Foo()
    {
        static size_t count = 0;
        this->count = count += 1;
    }
};
 0
Author: BSalita,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-07 19:17:38

Un constructor estático puede ser emulado usando 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
 0
Author: Jobin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-22 09:18:32

Wow, no puedo creer que nadie haya mencionado la respuesta más obvia, y una que imita más de cerca 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 se puede usar, se puede hacer con una variable de clase booleana estática y una operación atómica de comparación e intercambio. En su constructor, vea si puede cambiar atómicamente el indicador class-static de false a true, y si es así, puede ejecutar código de construcción estática.

Para obtener crédito extra, conviértalo en un indicador de 3 vías en lugar de un booleano, es decir, no ejecutar, correr y terminar de correr. A continuación, todas las demás instancias de esa clase pueden spin-lock hasta que la instancia que ejecuta el static-constructor haya terminado (es decir, emitir un memory-fence, luego establecer el estado en "done running"). 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 giro bastante estándar.

En la ausencia de C++11, esto debería ayudarte a empezar.

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();
 0
Author: ulatekh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-01 21:37:12