Palavra-chave static em C++
A palavra-chave static em C++ confere propriedades específicas a variáveis e funções. De acordo com a documentação oficial, ela afeta o armazenamento, a visibilidade e a associação a classes.
static com Variáveis Globais
Em arquivos de cabeçalho, variáveis globais podem causar erros de redefinição se incluídas em múltiplas unidades de tradução. A solução é usar extern no cabeçalho para declaração e definir a variável em um único arquivo de implementação.
// header.h
extern int globalVar;
// implementation.cpp
#include "header.h"
int globalVar = 42;
Quando aplicamos static a uma variável global, ela ganha linkage interno, tornando-se visível apenas dentro da unidade de tradução atual, mesmo com extern.
// header.h
static int staticGlobal = 10;
Cada unidade de tradução que incluir esse cabeçalho terá sua própria cópia de staticGlobal, como demonstrado pelos endereços diferentes impressos.
static com Variáveis Locais
Variáveis locais modificadas por static mantêm seu valor entre chamadas de função, com armazenamento estático durante toda a execução do programa.
#include <iostream>
void contador() {
static int vezes = 0;
vezes++;
std::cout << "Chamado " << vezes << " vezes\n";
}
int main() {
contador(); // Saída: Chamado 1 vezes
contador(); // Saída: Chamado 2 vezes
}
static com Membros de Classe
Membros estáticos pertencem à classe, não a instâncias específicas. Funções estáticas só podem acessar membros estáticos da classe.
class MinhaClasse {
public:
static void funcaoEstatica() {
std::cout << variavelEstatica;
// Erro: não pode acessar membros não estáticos
}
private:
static int variavelEstatica;
int variavelNaoEstatica;
};
Criando um Singleton
O padrão Singleton garante uma única instância global com um ponto de acesso controlado.
Singleton Eager (Inicialização Ansiosa)
O construtor é privado para prevenir criação externa. Um membro estático contém a instância, e uma função estática fornece acesso global.
class SingletonEager {
public:
static SingletonEager& obterInstancia() {
return instancia;
}
SingletonEager(const SingletonEager&) = delete;
SingletonEager& operator=(const SingletonEager&) = delete;
private:
SingletonEager() {}
static SingletonEager instancia;
};
Definição no arquivo .cpp:
SingletonEager SingletonEager::instancia;
Este modelo cria a instância durante a inicialização do programa, consumindo memória mesmo se não utilizada.
Singleton Lazy (Inicialização Preguiçosa)
A instância é criada apenas na primeira solicitação, usando um ponteiro e liberação manual ou automática.
#include <memory>
class SingletonLazy {
public:
static SingletonLazy& obterInstancia() {
if (!ponteiro) {
ponteiro.reset(new SingletonLazy);
}
return *ponteiro;
}
SingletonLazy(const SingletonLazy&) = delete;
SingletonLazy& operator=(const SingletonLazy&) = delete;
private:
SingletonLazy() {}
static std::unique_ptr<SingletonLazy> ponteiro;
};
std::unique_ptr<SingletonLazy> SingletonLazy::ponteiro;
Segurança em Multithread
Ambientes multithread podem causar criações múltiplas. Uma solução é usar mutex para sincronização.
#include <mutex>
class SingletonThreadSafe {
public:
static SingletonThreadSafe& obterInstancia() {
std::lock_guard<std::mutex> travar(mutexGlobal);
if (!ponteiro) {
ponteiro.reset(new SingletonThreadSafe);
}
return *ponteiro;
}
SingletonThreadSafe(const SingletonThreadSafe&) = delete;
SingletonThreadSafe& operator=(const SingletonThreadSafe&) = delete;
private:
SingletonThreadSafe() {}
static std::unique_ptr<SingletonThreadSafe> ponteiro;
static std::mutex mutexGlobal;
};
std::unique_ptr<SingletonThreadSafe> SingletonThreadSafe::ponteiro;
std::mutex SingletonThreadSafe::mutexGlobal;
A veriifcação dupla (DCLP) pode otimizar, mas problemas de reordenação de memória exigem cautela. Abordagens modernas como std::call_once ou variáveis locais estáticas são perferíveis.
Usando std::call_once
std::call_once garante execução única thread-safe desde C++11.
#include <mutex>
class SingletonCallOnce {
public:
static SingletonCallOnce& obterInstancia() {
static std::once_flag sinal;
std::call_once(sinal, []() {
ponteiro.reset(new SingletonCallOnce);
});
return *ponteiro;
}
SingletonCallOnce(const SingletonCallOnce&) = delete;
SingletonCallOnce& operator=(const SingletonCallOnce&) = delete;
private:
SingletonCallOnce() {}
static std::unique_ptr<SingletonCallOnce> ponteiro;
};
std::unique_ptr<SingletonCallOnce> SingletonCallOnce::ponteiro;
Singleton de Meyer
Utiliza variável estática local, aproveitando a inicialização única controlada pelo compilador.
class SingletonMeyer {
public:
static SingletonMeyer& obterInstancia() {
static SingletonMeyer instancia;
return instancia;
}
SingletonMeyer(const SingletonMeyer&) = delete;
SingletonMeyer& operator=(const SingletonMeyer&) = delete;
private:
SingletonMeyer() {}
};
Atenção: em cenários com múltiplas bibliotecas estáticas compartilhadas, cada módulo pode ter sua própria instância, querbando o padrão Singleton.
Conceitos Relacionados
Duração de Armazenamento e Linkage
A duração de armazenamento determina quando a memória é liberada: automática (escopo de bloco), estática (durante o programa), thread-local (C++11+) ou dinâmica (manual). O linkage define visibilidade: sem linkage (local), interno (dentro da unidade de tradução) ou externo (entre unidades).
Membros Estáticos em Classes
Membros estáticos de classe são compartilhados por todas as instâncias. Desde C++17, inline static permite definição dentro da classe. Membros const static podem ser inicializados inline se forem de tipos literais.
class MinhaClasse {
public:
inline static int contador = 0; // C++17
const static int MAXIMO = 100;
static void metodoEstatico();
private:
static double dados;
};