Em C++, o qualificador const é uma ferramenta poderosa para expressar a intenção de que um valor não deve ser alterado. Ele é uma promessa ao compilador que, se mantida, pode levar a um código mais seguro, otimizado e legível. Este artigo explora o uso do const em diferentes contextos, desde variáveis simples até classes e ponteiros complexos.
Variáveis Constantes
Quando declaramos uma variável com const, estamos garantindo que seu valor não poderá ser modificado após a inicialização. Tentar reatribuir um valor a uma variável const resultará em um erro de compilação.
#include <iostream>
int main() {
// Variável comum, pode ser alterada
int contador = 10;
std::cout << "Contador inicial: " << contador << std::endl;
contador = 20; // OK
std::cout << "Contador alterado: " << contador << std::endl;
// Variável constante, não pode ser alterada
const double PI_APROXIMADO = 3.14;
// PI_APROXIMADO = 3.14159; // ERRO: atribuição a uma variável somente leitura
std::cout << "PI Aproximado: " << PI_APROXIMADO << std::endl;
return 0;
}
const com Ponteiros
O uso de const com ponteiros pode ser um pouco mais complexo, pois ele pode qualificar o dado para o qual o ponteiro aponta, o próprio ponteiro, ou ambos.
Ponteiro para Dado Constante (const Tipo\* ou Tipo const\*)
Neste caso, o ponteiro aponta para um dado que não pode ser modificado através desse ponteiro. O ponteiro em si pode ser reatribuído para apontar para outro local de memória.
#include <iostream>
int main() {
int valor_original = 100;
const int VALOR_FIXO = 50;
const int* ponteiro_para_const = &valor_original; // Pode apontar para valor_original
std::cout << "Valor apontado (original): " << *ponteiro_para_const << std::endl;
// *ponteiro_para_const = 120; // ERRO: não pode modificar o dado apontado
ponteiro_para_const = &VALOR_FIXO; // OK: ponteiro pode ser reatribuído
std::cout << "Valor apontado (fixo): " << *ponteiro_para_const << std::endl;
return 0;
}
Ponteiro Constante para Dado (Tipo\* const)
Aqui, o ponteiro não pode ser reatribuído para apontar para outro local de memória após a inicialização, mas o dado para o qual ele aponta pode ser modificado através desse ponteiro.
#include <iostream>
int main() {
int vida_jogador = 100;
int pontuacao = 0;
int* const ponteiro_constante = &vida_jogador; // Ponteiro é constante, deve ser inicializado
std::cout << "Vida inicial: " << *ponteiro_constante << std::endl;
*ponteiro_constante = 75; // OK: pode modificar o dado apontado
std::cout << "Vida após dano: " << *ponteiro_constante << std::endl;
// ponteiro_constante = &pontuacao; // ERRO: não pode reatribuir o ponteiro
return 0;
}
Ponteiro Constante para Dado Constante (const Tipo\* const)
Nesta combinação, nem o dado apontado nem o próprio ponteiro podem ser modificados.
#include <iostream>
int main() {
const int NUMERO_MAGICO = 7;
const int* const ponteiro_totalmente_const = &NUMERO_MAGICO; // Ambos são constantes
std::cout << "Número Mágico: " << *ponteiro_totalmente_const << std::endl;
// *ponteiro_totalmente_const = 8; // ERRO: não pode modificar o dado
// ponteiro_totalmente_const = nullptr; // ERRO: não pode reatribuir o ponteiro
return 0;
}
Removendo a Constância com const\_cast
Embora o const seja uma promessa, C++ fornece o operador const\_cast para remover a qualificação const de um ponteiro ou referência. Isso é geralmente necessário ao interagir com APIs de C legadas ou em cenários muito específicos, mas deve ser usado com extrema cautela, pois pode levar a comportamento indefinido se usado para modificar um objeto que foi originalmente declraado como const.
#include <iostream>
void ImprimirEModificar(int* val) {
std::cout << "Valor original (dentro da função): " << *val << std::endl;
*val += 10;
std::cout << "Valor modificado (dentro da função): " << *val << std::endl;
}
int main() {
const int configuracao_padrao = 100;
std::cout << "Configuração padrão antes: " << configuracao_padrao << std::endl;
// const_cast permite remover a constância de um ponteiro
// Mas modificar 'configuracao_padrao' através do ponteiro resultante é comportamento indefinido
// se 'configuracao_padrao' foi definida como const desde o início.
// É mais seguro usar const_cast com dados não-const que foram passados como const.
int* ptr_modificavel = const_cast<int>(&configuracao_padrao);
// Isto é comportamento indefinido se 'configuracao_padrao' é realmente const.
// Demonstrando apenas a capacidade do const_cast, não seu uso seguro.
*ptr_modificavel = 150;
std::cout << "Configuração padrão após (via const_cast): " << configuracao_padrao << std::endl;
// Exemplo de uso mais seguro:
int var_nao_const = 200;
const int* ptr_para_var_nao_const = &var_nao_const;
// Podemos remover const para passar para uma função não-const
ImprimirEModificar(const_cast<int>(ptr_para_var_nao_const));
std::cout << "Variável não-const após função: " << var_nao_const << std::endl;
return 0;
}
</int></int>
const em Métodos de Classes
Ao aplicar const a um método de classe, estamos declarando que esse método não modificará o estado do objeto (ou seja, seus membros de dados não-mutable). Isso permite que o método seja chamado em objetos const.
#include <iostream>
#include <string>
class Produto {
private:
std::string nome;
double preco;
int quantidade;
public:
Produto(const std::string& n, double p, int q)
: nome(n), preco(p), quantidade(q) {}
// Método const: não modifica o estado do objeto
std::string GetNome() const {
// nome = "Novo Nome"; // ERRO: não pode modificar membros em um método const
return nome;
}
double GetPreco() const {
return preco;
}
// Método não-const: pode modificar o estado do objeto
void AjustarQuantidade(int delta) {
quantidade += delta;
}
int GetQuantidade() const {
return quantidade;
}
};
void ExibirDetalhes(const Produto& p) { // Recebe uma referência const
std::cout << "Produto: " << p.GetNome()
<< ", Preço: R$" << p.GetPreco()
<< ", Qtd: " << p.GetQuantidade() << std::endl;
// p.AjustarQuantidade(1); // ERRO: não pode chamar método não-const em objeto const
}
int main() {
Produto item1("Laptop", 1500.0, 5);
const Produto item2("Monitor", 800.0, 3); // Objeto const
ExibirDetalhes(item1);
ExibirDetalhes(item2);
item1.AjustarQuantidade(2); // OK: item1 não é const
// item2.AjustarQuantidade(1); // ERRO: item2 é const
std::cout << "Nova quantidade Laptop: " << item1.GetQuantidade() << std::endl;
return 0;
}
Sobrecarga de Métodos const e Não-const
É possível ter duas versões de um método, uma const e outra não-const. O compilador escolherá a versão apropriada dependendo se o objeto que chama o método é const ou não.
#include <iostream>
class Dados {
private:
int valor;
public:
Dados(int v) : valor(v) {}
// Versão não-const: retorna uma referência modificável
int& GetValor() {
std::cout << "Chamado GetValor() não-const" << std::endl;
return valor;
}
// Versão const: retorna uma referência const, não modificável
const int& GetValor() const {
std::cout << "Chamado GetValor() const" << std::endl;
return valor;
}
};
int main() {
Dados d_mutavel(10);
const Dados d_imutavel(20);
d_mutavel.GetValor() = 15; // Chama a versão não-const
// d_imutavel.GetValor() = 25; // ERRO: Chama a versão const, referência const
std::cout << "Valor mutável: " << d_mutavel.GetValor() << std::endl;
std::cout << "Valor imutável: " << d_imutavel.GetValor() << std::endl;
return 0;
}
O Qualificador mutable
O mutable é um qualificador de membro de classe que permite que um membro de dados seja modificado mesmo dentro de um método const. Isso é útil para otimizações (como caching) onde a modificação de um membro não altera o estado lógico ou observável do objeto.
#include <iostream>
#include <string>
class ElementoGrafico {
private:
std::string nome;
int posicao_x;
int posicao_y;
mutable int cache_calculado_hash; // Pode ser modificado em métodos const
mutable bool cache_valido;
void RecalcularHash() const {
// Simula um cálculo caro
cache_calculado_hash = posicao_x * 31 + posicao_y; // OK por causa de 'mutable'
cache_valido = true;
std::cout << "Hash recalculado." << std::endl;
}
public:
ElementoGrafico(const std::string& n, int x, int y)
: nome(n), posicao_x(x), posicao_y(y), cache_calculado_hash(0), cache_valido(false) {}
// Método const que pode modificar membros 'mutable'
int ObterHash() const {
if (!cache_valido) {
RecalcularHash();
}
return cache_calculado_hash;
}
void Mover(int novo_x, int novo_y) {
posicao_x = novo_x;
posicao_y = novo_y;
cache_valido = false; // Invalida o cache ao modificar o estado
}
};
int main() {
ElementoGrafico circulo("Círculo", 10, 20);
const ElementoGrafico quadrado("Quadrado", 5, 5); // Objeto const
std::cout << "Hash do Círculo: " << circulo.ObterHash() << std::endl; // Recalcula
std::cout << "Hash do Círculo (novamente): " << circulo.ObterHash() << std::endl; // Usa cache
std::cout << "Hash do Quadrado: " << quadrado.ObterHash() << std::endl; // Recalcula (mesmo sendo const)
quadrado.ObterHash(); // Usa cache
circulo.Mover(30, 40); // Modifica o objeto, invalidando o cache
std::cout << "Hash do Círculo após mover: " << circulo.ObterHash() << std::endl; // Recalcula
return 0;
}