construtores estáticos em C ++? Eu preciso inicializar objetos estáticos privados


176

Eu quero ter uma classe com um membro de dados estático privado (um vetor que contém todos os caracteres az). Em java ou C #, posso apenas criar um "construtor estático" que será executado antes de criar qualquer instância da classe e configurar os membros de dados estáticos da classe. Ele é executado apenas uma vez (como as variáveis ​​são somente leitura e precisam ser definidas apenas uma vez) e, como é uma função da classe, ele pode acessar seus membros privados. Eu poderia adicionar código no construtor que verifica se o vetor foi inicializado e inicializá-lo se não for, mas que introduz muitas verificações necessárias e não parece ser a solução ideal para o problema.

Ocorre-me que, uma vez que as variáveis ​​serão apenas de leitura, elas podem ser apenas constantes estáticas públicas, para que eu possa defini-las uma vez fora da classe, mas mais uma vez, parece um hack feio.

É possível ter membros de dados estáticos privados em uma classe se não quiser inicializá-los no construtor de instância?



1
@CiroSantilli Esta questão é focada na execução de código para inicializar objetos estáticos privados , não na definição de valores constantes de tipos primitivos estáticos privados. As soluções são diferentes.
Gordon Gustafson

ah, acho que você está certo, se retraindo.
Ciro Santilli escreveu:

Respostas:


180

Para obter o equivalente a um construtor estático, você precisa escrever uma classe comum separada para armazenar os dados estáticos e, em seguida, criar uma instância estática dessa classe comum.

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
obrigado! embora isso seja muito chato ter que fazer tudo isso. Um dos muitos "erros" que C # e java aprenderam.
18777 Gordon Gustafson

109
Sim. Eu sempre indico às pessoas que, se o C ++ não tivesse cometido todos esses "erros", outras línguas teriam que cometê-los. C ++ cobrindo tanto terreno, mesmo cometendo erros, tem sido ótimo para as linguagens que o seguiram.
quark

11
Apenas uma pequena nuance, pois os construtores entram em cena que ninguém garante quando o construtor do objeto estático é executado. Uma abordagem bem mais segura e conhecida é a classe Elsewhere {StaticStuff & get_staticStuff () {static StaticStuff staticStuff; // o construtor é executado uma vez, quando alguém primeiro precisa retornar staticStuff; }}; Pergunto-me se construtores estáticos em C # e Java pode fornecer a mesma garantia que o código acima ...
Oleg Zhylin

13
@ Oleg: Sim, eles fazem. O padrão garante que os construtores para todas as variáveis ​​não locais sejam executados antes que main seja inserido. Também garante que dentro de uma unidade de compilação a ordem de construção está bem definida e a mesma ordem que a declaração dentro da unidade de compilação. Infelizmente, eles não definem a ordem entre várias unidades de compilação.
Martin Iorque

13
Este é realmente um caso em que friendfaz muito sentido para que a classe Elsewherepossa acessar facilmente StaticStuffos internos (sem quebrar o encapsulamento de nenhuma maneira perigosa, devo acrescentar).
Konrad Rudolph

81

Bem, você pode ter

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

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

Não esqueça (no .cpp) isto:

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

O programa ainda será vinculado sem a segunda linha, mas o inicializador não será executado.


+1 (não testou) Mas: quando é chamado ctor _init._init ()? Antes ou depois do ctor do MyClass quando tenho um objeto estático MyClass? Eu acho que você não pode dizer ...
ur.

2
Olá, onde posso encontrar mais informações sobre essa mágica do "inicializador"?
Karel Bílek

Não deveria ser em MyClass::a.push_back(i)vez de a.push_back(i)?
Neel Basu

4
@ur .: _initializeré um subobjeto de MyClass. Os subobjetos são inicializados nesta ordem: subobjetos da classe base virtual, em ordem de profundidade, da esquerda para a direita (mas apenas inicializando cada subobjeto distinto uma vez); subobjetos simples da classe base, em ordem de profundidade, da esquerda para a direita; em seguida, subobjetos membros em ordem de declaração. Portanto, é seguro usar a estratégia do EFraim, desde que o código _initialiserse refira apenas aos membros declarados antes dele.
Jrandom_hacker

2
Essa resposta é melhor que a aceita porque o autor mencionou a inicialização indispensável no segundo clipe de código.
Jeff T.

33

Solução C ++ 11

Desde o C ++ 11, você pode simplesmente usar expressões lambda para inicializar membros de classe estáticos. Isso funciona mesmo se você precisar impor uma ordem de construção entre os vários membros estáticos ou se você tiver membros estáticos const.

Arquivo de cabeçalho:

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

Arquivo fonte:

// 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();

solução interessante. neste caso, se eu lançar uma exceção, quem pode pegá-la?
Rafi wiener

5
O código de inicialização do programa estático nunca deve gerar nenhuma exceção, ou o programa falhará. Você deve agrupar a lógica do inicializador em um try catchbloco se houver exceções.
usar o seguinte código

19

No arquivo .h:

class MyClass {
private:
    static int myValue;
};

No arquivo .cpp:

#include "myclass.h"

int MyClass::myValue = 0;

5
Isso funciona bem para membros estáticos individuais (independentemente do tipo). A deficiência em comparação com os construtores estáticos é que você não pode impor uma ordem entre os vários membros estáticos. Se você precisar fazer isso, consulte a resposta de Earwicker.
quark

Estou fazendo exatamente isso, mas ainda não é compilado. E ele diz que esta é a área do problema (no construtor, não o cabeçalho)
Flotolk

14

Aqui está outra abordagem semelhante à de Daniel Earwicker, também usando a sugestão de classe de amigo de Konrad Rudolph. Aqui, usamos uma classe de utilidade privada privada para inicializar os membros estáticos da sua classe principal. Por exemplo:

Arquivo de cabeçalho:

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

Arquivo de implementação:

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

Essa abordagem tem a vantagem de ocultar completamente a classe Initializer do mundo exterior, mantendo tudo o que está contido na classe a ser inicializado.


+1 Para dar um exemplo que mantém a implementação em seu próprio arquivo.
Andrew Larsson

1
Além disso, você deve certificar-se de que ToBeInitialized::Initializer::Initializer()é chamado, portanto, é necessário adicionar ToBeInitialized::Initializer ToBeInitialized::initializer;ao arquivo de implementação. Peguei algumas coisas da sua ideia e da EFraim, e funciona exatamente como eu preciso e parece limpo. Obrigado cara.
Andrew Larsson

11

Test::StaticTest() é chamado exatamente uma vez durante a inicialização estática global.

O chamador precisa adicionar apenas uma linha à função que deve ser seu construtor estático.

static_constructor<&Test::StaticTest>::c;força a inicialização de cdurante a inicialização 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 é uma solução fantástica. Também gosto muito da resposta de Douglas Mandel , mas isso é ainda mais conciso.
FlintZA

Isso é realmente incrível!
nh_ 06/04/19

9

Não é necessário criar uma init()função a std::vectorpartir de um intervalo:

// 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 ) );

Observe, no entanto, que as estáticas do tipo de classe causam problemas nas bibliotecas, portanto devem ser evitadas lá.

Atualização do C ++ 11

A partir do C ++ 11, você pode fazer isso:

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

É semanticamente equivalente à solução C ++ 98 na resposta original, mas você não pode usar uma string literal no lado direito, portanto não é completamente superior. No entanto, se tiver um vector de qualquer outro tipo de char, wchar_t, char16_tou char32_t(matrizes das quais podem ser escritas como sequências de caracteres), a versão C ++ 11 irá remover rigorosamente código padrão sem introduzir outra sintaxe, em comparação com o C ++ 98 versão.


Eu gosto disso. Embora se pudéssemos fazê-lo em uma linha sem o alfabeto agora inútil.
Martin York

Por causar problemas com as bibliotecas, importa se a classe estática é privada ou pública? Além disso, importa se a biblioteca é estática (.a) ou dinâmica (.so)?
Zachary Kraus

@ZacharyKraus: o que é uma aula pública / privada ? E não, embora os problemas sejam diferentes, mas se sobreponham, não importa se a biblioteca está vinculada estaticamente ou dinamicamente.
Marc Mutz - mmutz

@ MarcMutz-mmutz Desculpe por usar a classe pública / privada que não está correta na terminologia C ++. O que eu estava me referindo é a solução da EFraim acima. Na minha versão, porém, tornei o membro da classe estático privado. Eu estava tentando entender se ter um membro da classe estática como público ou privado faz a diferença no desenvolvimento e na usabilidade da biblioteca. Meu instinto me diz que não deve afetar a biblioteca porque os usuários nunca terão acesso ao membro da classe estática ou ao objeto que está construindo, mas eu gostaria de ter a sabedoria de algum guru sobre esse tópico.
Zachary Kraus

@ZacharyKraus: O principal problema com as estáticas que requerem inicialização dinâmica ([basic.start.init] / 2) é que elas executam código. Nas bibliotecas, pode ser que o código da biblioteca já tenha sido descarregado quando os destruidores são executados. Se você quiser ouvir mais, sugiro postar uma pergunta sobre isso.
Marc Mutz - mmutz

6

O conceito de construtores estáticos foi introduzido em Java depois que eles aprenderam com os problemas em C ++. Portanto, não temos equivalente direto.

A melhor solução é usar tipos de POD que podem ser inicializados explicitamente.
Ou torne seus membros estáticos um tipo específico que tenha seu próprio construtor que irá inicializá-lo corretamente.

//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

Ao tentar compilar e usar a classe Elsewhere( da resposta de Earwicker ), recebo:

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

Parece que não é possível inicializar atributos estáticos de tipos não inteiros sem colocar algum código fora da definição de classe (CPP).

Para fazer essa compilação, você pode usar " um método estático com uma variável local estática dentro ". Algo assim:

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

E você também pode passar argumentos para o construtor ou inicializá-lo com valores específicos, é muito flexível, poderoso e fácil de implementar ... a única coisa é que você tem um método estático contendo uma variável estática, não um atributo estático ... A sintaxe muda um pouco, mas ainda é útil. Espero que isso seja útil para alguém,

Hugo González Castro.


Embora tenha cuidado se estiver usando threads. Acredito que no GCC a construção de locais estáticos é protegida contra a execução simultânea, mas no Visual C ++ não é.
Daniel Earwicker

1
A partir do C ++ 11 em diante, e no POSIX, ele deve ser seguro para threads.
Marc Mutz - mmutz

Gostei bastante de duas outras soluções acima ( isso e isso ), mas a sua é a única que garante a inicialização das estáticas na ordem em que são necessárias nas bibliotecas. Eu só tenho um método de Instância estática privada como o seu acima e acesso a outros valores em acessadores estáticos públicos que usam esse método de Instância em vez de referências diretas. Obrigado.
FlintZA

Impressionante! Isso completa.
Gabe Halsmer 6/09/16

4

Eu acho que a solução simples para isso 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;
    }

É assim que eu faço também.
21313 Etherealone

1

Apenas resolvi o mesmo truque. Eu tive que especificar a definição de um único membro estático para Singleton. Mas torne as coisas mais complicadas - decidi que não quero chamar o ctor de RandClass (), a menos que eu o use ... é por isso que não quero inicializar singleton globalmente no meu código. Também adicionei uma interface simples no meu caso.

Aqui está o código final:

Simplifiquei o código e uso a função rand () e seu único inicializador 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

Aqui está minha variante da solução da EFraim; a diferença é que, graças à instanciação implícita do modelo, o construtor estático é chamado apenas se instâncias da classe são criadas e que nenhuma definição no .cpparquivo é necessária (graças à mágica da instanciação do modelo).

No .harquivo, você tem:

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;

No .cpparquivo, você pode ter:

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

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

Observe que MyClass::aé inicializado apenas se a linha [1] estiver lá, porque isso chama (e requer instanciação) do construtor, que requer instanciação de _initializer.


1

Aqui está outro método, em que o vetor é privado para o arquivo que contém a implementação usando um espaço para nome anônimo. É útil para coisas como tabelas de pesquisa que são privadas para a implementação:

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

Embora você queira nomear Ie ialgo um pouco mais obscuro para não usá-los acidentalmente em algum lugar mais baixo do arquivo.
21712 Jim Hunziker

1
Para ser honesto, é difícil ver por que alguém iria querer usar membros estáticos privados, em vez de espaços de nomes anônimos em arquivos de implementação.
21712 Jim Hunziker

1

Certamente não precisa ser tão complicado quanto a resposta atualmente aceita (por Daniel Earwicker). A turma é supérflua. Não há necessidade de uma guerra de idiomas neste caso.

Arquivo .hpp:

vector<char> const & letters();

arquivo .cpp:

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


0

Você define variáveis ​​de membro estáticas da mesma forma que define métodos de membro.

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
Pergunta CrazyJugglerDrummer foi não sobre um tipo estático planície dados antigos :)
JWW

0

Para inicializar uma variável estática, basta fazê-lo dentro de um arquivo de origem. Por exemplo:

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


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

Pergunta CrazyJugglerDrummer foi não sobre um tipo estático planície dados antigos :)
JWW

0

Que tal criar um modelo para imitar o comportamento do 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 aqui, uma variável estática envolvida dentro de uma função de membro estática é quase tão boa. É simples e geralmente será otimizado pelos compiladores. Porém, isso não resolve o problema da ordem de inicialização de objetos complexos.

#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

Isso é uma solução?

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

0

Um construtor estático pode ser emulado usando uma classe de amigo ou classe aninhada como abaixo.

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

Resultado:

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 que você está newinserindo uma matriz de caracteres apenas para vazar imediatamente o ponteiro e substituí-lo !?
Eric

0

Uau, não acredito que ninguém tenha mencionado a resposta mais óbvia e que imita mais de perto o comportamento do construtor estático do C #, ou seja, ele não é chamado até que o primeiro objeto desse tipo seja criado.

std::call_once()está disponível no C ++ 11; se você não pode usar isso, isso pode ser feito com uma variável de classe booleana estática e uma operação atômica de comparação e troca. No seu construtor, veja se você pode alterar atomicamente o sinalizador estático de classe de falseparatrue e, se sim, pode executar o código de construção estática.

Para obter crédito extra, torne-o um sinalizador de três vias em vez de um booleano, ou seja, não execute, execute e execute. Todas as outras instâncias dessa classe podem girar com trava até que a instância que está executando o estático-construtor tenha terminado (por exemplo, emita um limite de memória e defina o estado como "concluído a execução"). Seu bloqueio de rotação deve executar a instrução de "pausa" do processador, dobrar a espera de cada vez até um limite, etc. - técnica bastante comum de bloqueio de rotação.

Na ausência do C ++ 11, isso deve começar.

Aqui estão alguns pseudocódigo para guiá-lo. Coloque isso na sua definição de classe:

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

E isso no seu construtor:

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();
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.