Como inicializar membros estáticos privados em C ++?

520

Qual é a melhor maneira de inicializar um membro de dados estático privado em C ++? Eu tentei isso no meu arquivo de cabeçalho, mas isso me dá erros estranhos no vinculador:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

Acho que é porque não consigo inicializar um membro privado de fora da classe. Então, qual é a melhor maneira de fazer isso?

Jason Baker
fonte
2
Oi Jason. Não encontrei um comentário sobre a inicialização padrão de membros estáticos (especialmente os integrantes). De fato, você precisa escrever int foo :: i para que o vinculador possa encontrá-lo, mas ele será automaticamente inicializado com 0! Essa linha seria suficiente: int foo :: i; (Isto é válido para todos os objetos armazenados na memória estática, o linker é responsável por inicializar os objetos estáticos.)
Nico
1
As respostas abaixo não se aplicam a uma classe de modelo. Eles dizem: a inicialização deve ir para o arquivo de origem. Para uma classe de modelo, isso não é possível nem necessário.
Joachim W
7
C ++ 17 permite a inicialização em linha de membros de dados estáticos (mesmo para tipos não inteiros): inline static int x[] = {1, 2, 3};. Veja en.cppreference.com/w/cpp/language/static#Static_data_members
Vladimir Reshetnikov

Respostas:

557

A declaração da classe deve estar no arquivo de cabeçalho (ou no arquivo de origem, se não for compartilhado).
Arquivo: foo.h

class foo
{
    private:
        static int i;
};

Mas a inicialização deve estar no arquivo de origem.
Arquivo: foo.cpp

int foo::i = 0;

Se a inicialização estiver no arquivo de cabeçalho, cada arquivo que incluir o arquivo de cabeçalho terá uma definição do membro estático. Assim, durante a fase do link, você receberá erros do vinculador, pois o código para inicializar a variável será definido em vários arquivos de origem. A inicialização do static int ideve ser feita fora de qualquer função.

Nota: Matt Curtis: pontos que C ++ permite a simplificação do acima, se a variável de membro estático é do tipo int const (por exemplo int, bool, char). Você pode declarar e inicializar a variável de membro diretamente dentro da declaração de classe no arquivo de cabeçalho:

class foo
{
    private:
        static int const i = 42;
};
Martin York
fonte
4
Sim. Mas suponho que a questão tenha sido simplificada. Tecnicamente, a declaração e a definição podem estar em um único arquivo de origem. Mas isso limita o uso da classe por outras classes.
Martin York
11
na verdade, não apenas POD, ele tem que ser um tipo int, bem como (int, short, bool, char ...)
Matt Curtis
9
Observe que isso não é apenas uma questão de como o valor é inicializado: tipos de integ integ constantes definidos como esse podem ser transformados em constantes de tempo de compilação pela implementação. Isso nem sempre é o que você deseja, pois aumenta a dependência binária: o código do cliente precisa de recompilação se o valor for alterado.
Steve Jessop
5
@ Martin: além da correção s / POD / integral /, se o endereço já tiver sido tomado, será necessário definir também. Por mais estranho que possa parecer, a declaração com inicializador, na definição de classe, não é uma definição. O idioma constante do modelo fornece uma solução alternativa para os casos em que você precisa da definição em um arquivo de cabeçalho. Outra solução alternativa mais simples é uma função que produz o valor de uma constante estática local. Cheers & hth.,
Cheers e hth. - Alf
3
Você pode adicionar um esclarecimento de que int foo :: i = 0; não deve estar dentro de uma função (incluindo a função principal). Eu tinha isso no início da minha função principal e não é assim.
precisa saber é o seguinte
89

Para uma variável :

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Isso ocorre porque só pode haver uma instância foo::ino seu programa. É o equivalente extern int iem um arquivo de cabeçalho eint i em um arquivo de origem.

Para uma constante, você pode colocar o valor diretamente na declaração de classe:

class foo
{
private:
    static int i;
    const static int a = 42;
};
Matt Curtis
fonte
2
Este é um ponto válido. Vou acrescentar isso também a minha explicação. Mas deve-se notar que isso funciona apenas para tipos de POD.
Martin York
Desde quando, o C ++ permite ser bom com declarações em classe e sem definição para tipos integrais. Desde o próprio C ++ 98 ou C ++ 03 ou quando? Por favor, compartilhe links autênticos, por favor. A redação padrão do C ++ não está sincronizada com os compiladores. Eles mencionam que o membro ainda deve ser definido se for usado. Portanto, eu não preciso da citação do C ++ Standard
smRaj
1
Eu me pergunto por que privatevariáveis ​​podem ser inicializadas fora da Classe aqui, isso também pode ser feito para variáveis ​​não estáticas.
Krishna Oza
Você encontrou a explicação? @Krishna_Oza
nn0p 14/09/16
@ nn0p ainda não, mas a inicialização de variáveis ​​privadas não estáticas fora Classnão faz sentido no Cpp.
Krishna Oza
41

Desde o C ++ 17, os membros estáticos podem ser definidos no cabeçalho com a palavra-chave inline .

http://en.cppreference.com/w/cpp/language/static

"Um membro de dados estáticos pode ser declarado em linha. Um membro de dados estáticos em linha pode ser definido na definição de classe e pode especificar um inicializador de membro padrão. Ele não precisa de uma definição fora da classe:"

struct X
{
    inline static int n = 1;
};
Morra em Sente
fonte
1
Isso é possível desde o C ++ 17, que está atualmente em andamento para se tornar o novo padrão.
Grebu 21/07/19
31

Para os futuros espectadores desta pergunta, quero ressaltar que você deve evitar o que monkey0506 está sugerindo .

Os arquivos de cabeçalho são para declarações.

Os arquivos de cabeçalho são compilados uma vez para cada .cpparquivo que os direta ou indiretamente #includes, e o código fora de qualquer função é executado antes da inicialização do programa main().

Ao colocar: foo::i = VALUE;no cabeçalho, foo:iserá atribuído o valor VALUE(qualquer que seja) a cada .cpparquivo, e essas atribuições ocorrerão em uma ordem indeterminada (determinada pelo vinculador) antes da main()execução.

E se formos #define VALUEum número diferente em um de nossos .cpparquivos? Ele irá compilar bem e não teremos como saber qual deles vence até executar o programa.

Nunca coloque o código executado em um cabeçalho pelo mesmo motivo que você nunca possui #includeum .cpparquivo.

incluir guardas (que eu concordo que você deve sempre usar) protegem você de algo diferente: o mesmo cabeçalho sendo indiretamente #included várias vezes ao compilar um único .cpparquivo

Joshua Clayton
fonte
2
Você está certo sobre isso, é claro, exceto no caso de um modelo de classe (que não é questionado, mas por acaso estou lidando com muita coisa). Portanto, se a classe estiver totalmente definida e não for um modelo de classe, coloque esses membros estáticos em um arquivo CPP separado, mas, para modelos de classe, a definição deve estar na mesma unidade de conversão (por exemplo, o arquivo de cabeçalho).
monkey0506
@ monkey_05_06: Isso parece ser um argumento para evitar membro estático no código do modelo: você já acaba com um membro estático para cada instanciação da classe. o problema é agravado, possivelmente compilando o cabeçalho em vários arquivos cpp ... Você pode obter uma série de definições conflitantes.
27713 Joshua Clayton
publib.boulder.ibm.com/infocenter/macxhelp/v6v81/… Este link descreve a instanciação de membros estáticos do modelo na função principal, que é mais limpa, se for um fardo.
27713 Joshua Clayton
1
Seu argumento é realmente enorme. Primeiro, você não pode # definir VALUE porque o nome das macros não pode ser um identificador válido. E mesmo se você pudesse - quem faria isso? Os arquivos de cabeçalho são para declaração -? Vamos lá .. Os únicos casos em que você deve evitar colocar valores no cabeçalho são combater odr-used. E colocar o valor no cabeçalho pode levar à recompilação desnecessária sempre que você precisar alterar o valor.
Aleksander Fular
20

Com um compilador da Microsoft [1], variáveis ​​estáticas que não são intsemelhantes também podem ser definidas em um arquivo de cabeçalho, mas fora da declaração de classe, usando o específico da Microsoft __declspec(selectany).

class A
{
    static B b;
}

__declspec(selectany) A::b;

Note que não estou dizendo que isso seja bom, apenas digo que isso pode ser feito.

[1] Atualmente, mais compiladores do que o MSC suportam __declspec(selectany)- pelo menos gcc e clang. Talvez até mais.

Johann Gerell
fonte
17
int foo::i = 0; 

É a sintaxe correta para inicializar a variável, mas ela deve estar no arquivo de origem (.cpp) e não no cabeçalho.

Por ser uma variável estática, o compilador precisa criar apenas uma cópia dela. Você precisa ter uma linha "int foo: i" em algum lugar do código para informar ao compilador onde colocá-lo, caso contrário, você receberá um erro de link. Se isso estiver em um cabeçalho, você receberá uma cópia em cada arquivo que inclui o cabeçalho; portanto, obtenha erros de símbolos definidos multiplicados no vinculador.

David Dibben
fonte
12

Não tenho representante suficiente aqui para adicionar isso como um comentário, mas, na IMO, é bom estilo escrever seus cabeçalhos com #include guardas de qualquer maneira, o que, como observado por Paranaix há algumas horas, impediria um erro de definição múltipla. A menos que você já esteja usando um arquivo CPP separado, não é necessário usá-lo apenas para inicializar membros não integrais estáticos.

#ifndef FOO_H
#define FOO_H
#include "bar.h"

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

Não vejo necessidade de usar um arquivo CPP separado para isso. Claro, você pode, mas não há nenhuma razão técnica para isso.

monkey0506
fonte
21
#include guardas apenas impedem várias definições por unidade de tradução.
Paul Fultz II
3
sobre bom estilo: você deve adicionar um comentário sobre o endif de fechamento:#endif // FOO_H
Riga
9
Isso funciona apenas se você tiver apenas uma unidade de compilação que inclui foo.h. Se dois ou mais cpps incluírem foo.h, que é uma situação típica, cada cpp declarará a mesma variável estática para que o vinculador se queixe com várias definições de `foo :: i ', a menos que você use uma compilação de pacotes com os arquivos (compile somente um arquivo que inclua todos os cpps). Mas, embora a compilação de pacotes seja ótima, a solução para o problema é declarar (int foo :: i = 0;) em um cpp!
Alejadro Xalabarder
1
Ou apenas use#pragma once
tambre 25/10/16
12

Se você deseja inicializar algum tipo de composto (string fe), pode fazer algo assim:

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 o método ListInitializationGuardé uma variável estática SomeClass::getList(), ele será construído apenas uma vez, o que significa que o construtor é chamado uma vez. Isso initialize _listvaria para o valor que você precisa. Qualquer chamada subsequente para getListsimplesmente retornará o _listobjeto já inicializado .

Claro que você deve acessar o _listobjeto sempre chamando getList()método.

Kris Kwiatkowski
fonte
1
Aqui está uma versão desse idioma que não requer a criação de um método por objeto de membro: stackoverflow.com/a/48337288/895245
Ciro Santilli
9

Padrão de construtor estático C ++ 11 que funciona para vários objetos

Um idioma foi proposto em: https://stackoverflow.com/a/27088552/895245 mas aqui está uma versão mais limpa que não requer a criação de um novo método por membro.

main.cpp

#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 upstream .

Compile e execute:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

Veja também: construtores estáticos em C ++? Eu preciso inicializar objetos estáticos privados

Testado no Ubuntu 19.04.

Variável inline C ++ 17

Mencionado em: https://stackoverflow.com/a/45062055/895245, mas aqui está um exemplo executável de vários arquivos para torná-lo ainda mais claro: como as variáveis ​​embutidas funcionam?

Ciro Santilli adicionou uma nova foto
fonte
5

Você também pode incluir a atribuição no arquivo de cabeçalho se usar proteções de cabeçalho. Eu usei essa técnica para uma biblioteca C ++ que eu criei. Outra maneira de obter o mesmo resultado é usar métodos estáticos. Por exemplo...

class Foo
   {
   public:
     int GetMyStatic() const
     {
       return *MyStatic();
     }

   private:
     static int* MyStatic()
     {
       static int mStatic = 0;
       return &mStatic;
     }
   }

O código acima tem o "bônus" de não exigir um arquivo de origem / CPP. Novamente, um método que eu uso para minhas bibliotecas C ++.


fonte
4

Eu sigo a ideia de Karl. Gosto e agora também o uso. Eu mudei um pouco a notação e adicionei algumas 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));
}

isso gera

mystatic value 7
mystatic value 3
is my static 1 0
Alejadro Xalabarder
fonte
3

Também trabalhando no arquivo 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
andrew
fonte
3

Que tal um set_default()método?

class foo
{
    public:
        static void set_default(int);
    private:
        static int i;
};

void foo::set_default(int x) {
    i = x;
}

Nós apenas teríamos que usar o set_default(int x)método e nossa staticvariável seria inicializada.

Isso não estaria em desacordo com o restante dos comentários, na verdade, segue o mesmo princípio de inicializar a variável em um escopo global, mas, usando esse método, a tornamos explícita (e fácil de entender) em vez de ter a definição da variável pendurada lá.

Arturo Ruiz Mañas
fonte
3

O problema do vinculador que você encontrou provavelmente é causado por:

  • Fornecendo definição de membro estático e de classe no arquivo de cabeçalho,
  • Incluindo esse cabeçalho em dois ou mais arquivos de origem.

Este é um problema comum para quem começa com C ++. O membro da classe estática deve ser inicializado na unidade de conversão única, ou seja, no arquivo de origem único.

Infelizmente, o membro estático da classe deve ser inicializado fora do corpo da classe. Isso complica a escrita de código somente de cabeçalho e, portanto, estou usando uma abordagem bem diferente. Você pode fornecer seu objeto estático através da função de classe estática ou não estática, por exemplo:

class Foo
{
    // int& getObjectInstance() const {
    static int& getObjectInstance() {
        static int object;
        return object;
    }

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};
ninguém especial
fonte
1
Ainda sou um n00b completo no que diz respeito ao C ++, mas isso parece brilhante para mim, muito obrigado! Recebo o gerenciamento perfeito do ciclo de vida do objeto singleton gratuitamente.
Rafael Kitover 6/11/19
2

Uma maneira "antiga" de definir constantes é substituí-las por enum:

class foo
{
    private:
        enum {i = 0}; // default type = int
        enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

Dessa forma, não é necessário fornecer uma definição e evita fazer o valor l constante , o que pode poupar algumas dores de cabeça, por exemplo, quando você acidentalmente usa o ODR .

anatolyg
fonte
1

Eu só queria mencionar algo um pouco estranho para mim quando encontrei isso pela primeira vez.

Eu precisava inicializar um membro de dados estático privado em uma classe de modelo.

no .h ou .hpp, é algo parecido com isto para inicializar um membro de dados estático de uma classe de modelo:

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;
Tyler Heers
fonte
0

Isso serve ao seu 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;
}
David Nogueira
fonte