Como ter membros de dados estáticos em uma biblioteca somente de cabeçalho?
-
13-12-2019 - |
Pergunta
Qual é a melhor maneira de ter um membro estático em uma classe de biblioteca não modelada, sem colocar o ônus de definir o membro no usuário da classe?
Digamos que eu queira fornecer esta classe:
class i_want_a_static_member
{
static expensive_resource static_resource_;
public:
void foo()
{
static_resource_.bar();
}
};
Então o usuário da classe não deve esquecer de definir o membro estático em algum lugar (como já respondidas muitos vezes):
// this must be done somewhere in a translation unit
expensive_resource i_want_a_static_member::static_resource_;
Eu tenho uma resposta abaixo, mas ela tem algumas desvantagens.Existem soluções melhores e/ou mais elegantes?
Solução
C++17 e superior
Usar inline static
variáveis para inicialização não dinâmica:
struct Foo
{
inline static int I = 0;
};
E use variáveis estáticas locais de função caso contrário:
struct Foo
{
static std::string& Bar()
{
static std::string S = compute();
return S;
}
};
C++14 e abaixo
Use estática local de função, pois elas são mais fáceis de usar.
Se por algum motivo você realmente deseja uma estática membro de dados, então você pode usar o truque do modelo:
template <typename T = void>
struct Foo
{
static int I = 0; // inline initialization only for simple types.
};
template <typename T>
int Foo<T>::I;
Na estática local
Para recursos que requerem inicialização dinâmica, é melhor usar uma estática local.
A ordem na qual as estáticas do escopo de arquivo ou de classe são inicializadas dinamicamente é indefinida, em geral, levando ao fiasco da ordem de inicialização estática quando você tenta ler uma estática não inicializada como parte da inicialização de outra.A estática local resolve o problema sendo inicializada lentamente, no primeiro uso.
No entanto, há uma pequena sobrecarga no uso da estática local.Do C++ 11 em diante, a inicialização deve ser thread-safe, o que normalmente significa que qualquer acesso é controlado por uma leitura atômica e uma ramificação bem prevista.
Outras dicas
Minha própria solução é usar uma classe de suporte modelada, já que membros estáticos funcionam bem em modelos, e usar esse suporte como classe base.
template <typename T>
struct static_holder
{
static T static_resource_;
};
template <typename T>
T static_holder<T>::static_resource_;
Agora use a classe titular:
class expensive_resource { /*...*/ };
class i_want_a_static_member : private static_holder<expensive_resource>
{
public:
void foo()
{
static_resource_.bar();
}
};
Mas como o nome do membro é especificado na classe titular, você não pode usar o mesmo titular para mais de um membro estático.
A partir do C++ 17.Agora você pode usar variáveis inline para fazer isso:
static const inline float foo = 1.25f;