Variáveis const em C++: aprenda a usar const e constexpr

Para que serve o const nas variáveis em c++
Variáveis constantes em C++: const, constexpr e mais!

Introdução

Bem-vindos de volta a mais um artigo sobre variáveis em C++. No artigo da Parte 2, tratei da inicialização de variáveis, dos escopos de nomes e, brevemente, dos namespaces. Desta vez, falarei das variáveis const em C++, e buscarei responder às seguintes perguntas: o que são e para que servem? Como são utilizadas? A que devo ficar atento ao usá-las?

O que são as variáveis const em C++?

const em C++ é um modificador de tipos de variáveis. Ele é utilizado para indicar que o valor da variável em questão não será modificado após sua inicialização, ou seja, que a variável tem valor constante durante toda sua vida no programa. Assim sendo, o modificador const tem um valor informativo ao usuário do código, pois ao se deparar com uma variável declarada como const, ele já sabe que não deve tentar modificá-la de forma alguma (supondo que fosse possível fazê-lo sem algum truque).

Como criar variáveis const em C++?

Vejamos abaixo um exemplo de utilização de uma variável const que armazena um valor de data de nascimento (algo que não queremos que seja alterado).

Uma variável cujo tipo é const também é chamada de variável constante.


Exemplo 1 – Criação de variáveis const em C++

#include <iostream>
#include <string>

int main() {
    // A dataDeNascimento não pode ser alterada, o nome pode.
    const std::string dataDeNascimento{"10-10-2010"};
    std::string nomeDaCrianca{"Fotocópia"};

    int anoAtual = 2029;
    nomeDaCrianca = "Maria";
    // Erro - não é permitido mudar o valor de uma variável const
    dataDeNascimento = "10-10-2000";

    return 0;
}

Regras do bom uso de const em C++

Por não poderem ter seus valores alterados após a inicialização, as variáveis const em C++ devem ser inicializadas durante a declaração. Caso uma variável constante não seja inicializada, haverá um erro durante a compilação semelhante ao seguinte: error: uninitialized 'const dataDeNascimento' [-fpermissive] . Vale a pena notar algo nesta mensagem de erro: no final há a flag de compilação -fpermissive, que serve para transformar esse erro em um warning, que apenas adverte o usuário por não inicializar uma variável const, mas não interrompe a compilação. Todavia, a menos que haja uma razão muito boa para fazê-lo, o uso da flag -fpermissive não é recomendado, pois pode levar a comportamentos imprevisíveis no código ao permitir-lhe que existam variáveis não incializadas.

int main() {
    // Erro - variável const int diasDeGloria não inicializada
    const int diasDeGloria;
    const long int diasDeLuta = 10000000000;

    return 0;
}

Uso de variáveis const em C++

Por via de regra, a maioria das operações que podem ser realizadas com uma variável normal também podem ser realizadas com uma variável constante, com exceção – claro – daquelas que modificariam o valor da variável. Até mesmo conversões para bool, como mostrado no exemplo a seguir, funcionam normalmente com variáveis const. Também é permitido inicializar variáveis do mesmo tipo que a variável constante a partir dela, pois a inicialização cria uma cópia da variável à direita do símbolo =, e após isso não há mais nenhuma ligação entre a variável const e a nova variável (veja as linhas de 13 a 17)


Exemplo 2 – Utilizando variáveis const em C++

#include <iostream>

int main() {
    const int salarioDevJunior = 0;

    // Conversão para bool também funciona com consts
    // Qualquer valor diferente de 0 é convertido
    // para true; 0 é convertido para false.
    bool aVidaTaFacil = salarioDevJunior;
    if (!aVidaTaFacil)
        std::cout << "1) Não ta fácil para ninguém." << std::endl;

    int salarioDevPleno = salarioDevJunior + 5000;
    aVidaTaFacil = salarioDevPleno;
    if (aVidaTaFacil)
        std::cout << "2) Por que choras, dev junior?" << std::endl;
    std::cout << "3) Salario do dev junior: R$" << salarioDevJunior << std::endl;
    
    return 0;
}

1) Não ta fácil para ninguém.
2) Por que choras, dev junior?
3) Salario do dev junior: R$0


Um outro ponto importante acerca das variáveis constantes é que elas podem ser inicializadas a partir do retorno de funções, ou seja, durante a execução do programa, e não durante sua compilação (isso é chamado de run-time initialization, em inglês). No exemplo abaixo, variavelComp tem seu valor atribuído durante a compilação do programa, mas variavelExec só terá seu valor inicializado quando o programa for executado, após a compilação, e não se pode saber o seu valor até que a função numeroAleatorio termine sua execução.


Exemplo 3 – Inicialização de variáveis durante a execução do programa

#include <iostream>
#include <cstdlib>

using namespace std;
// Essa função gera um número aleatório
// entre 1 e 100.
int numeroAleatorio() {
    return (rand() % 100 + 1);
}

int main() {
    const int variavelComp = 50;
    const int variavelExec = numeroAleatorio();
    cout << "1) Variável inicializada durante a compilação: " << variavelComp << endl;
    cout << "2) Variável inicializada durante a execução: " << variavelExec << endl;
    return 0;
}

1) Variável inicializada durante a compilação: 50
2) Variável inicializada durante a execução: 84


Constexpr: um outro tipo de const

As variáveis constantes podem também ser declaradas utilizando um outro modificador: constexpr. Esse irmão mais elegante do const em C++ serve ao mesmo propósito deste último, mas difere dele em um ponto importante: ele só tem efeito se a variável for inicializada por uma expressão constante (como um valor literal, por exemplo).

Caneta e papel para indicar termos importantes ligados às variáveis const em C++

Termos importantes

  • Expressão – uma expressão é normalmente a menor unidade de computação de um programa. Expressões são constituídas por ao menos um operando e, na maioria dos casos, um operador. Por exemplo, na instrução a seguir os operandos da expressão são os literais 10 e 20, e o operador é o sinal << + >>: 10 + 20; (evidentemente a instrução não tem nenhuma utilidade).
  • Literal – Um literal é um elemento do programa cujo valor é evidente por si próprio, como o 800 da expressão int calorias = 800;, ou o caractere ‘x’ em const char marcador = 'x';. Os tipos dos literais são determinados pelo seu valor e pela sua forma. Um tipo especial de literal é o string literal, que contém uma cadeia de caracteres dentre de um par de aspas duplas: string stringLiteral = "Isto é um string literal";
  • Expressão constante – é uma expressão cujo valor pode ser avaliado (ou determinado) durante a compilação do programa, e cujo valor não pode ser modificado. O exemplo mais comum de expressão constante é o valor literal. As expressões constantes (inclusive as variáveis que se comportam como tal) são diretamente substituídas no código pelo seu valor durante a compilação.

Na realidade, as variáveis const em C++ se comportam como se fossem constexpr se forem inicializadas a partir de expressões constantes. Tomemos o exemplo abaixo para compreender melhor esse comportamento.


Exemplo 4 – Variáveis inicializadas com expressões constexpr

// distanciaEmKm é inicializada a partir de um literal inteiro, logo é uma constexpr
const int distanciaEmKm = 10; // é constexpr

// distanciaEmMilhas é inicializada a partir de uma expressão constante (uma
// expressão envolvendo uma expressão constante e um literal inteiro resulta em uma
// expressão constante), portanto também é constexpr
const double distanciaEmMilhas = distanciaEmKm / 2; // é constexpr

// distanciaEmMetros é inicializada por uma expressão constante, mas
// é um int, e não const int, portanto não é constexpr
int distanciaEmMetros = distanciaEmKm * 1000; // não é constexpr

// distanciaSurpresa não é uma constexpr porque é inicializada durante
// a execução do programa através da chamada da função obtemDistancia()
constexpr double distanciaSurpresa = obtemDistancia(); // não é constexpr

Para que serve o constexpr?

Como é possível ver acima, as variáveis const em C++ podem se comportar como variáveis constexpr, então uma pergunta naturalmente se levanta: para que serve realmente o modificador constexpr? Para responder a essa pergunta, irei mostrar-lhes um caso que omiti propositalmente no exemplo anterior. Nele, vê-se o modificador constexpr utilizado sobre o tipo de retorno da função obterDiaDeNascimento; sim, isso é válido: o retorno de uma função pode ser uma expressão constante, contanto que o valor retornada seja ele próprio uma expressão constante (um literal como o valor 10 satisfaz essa condição).

#include <iostream>

constexpr int obterDiaDeNascimento() {
    return 10;
}

int main() {
    // diaDeNascimento é contexpr, pois o retorno da função
    // é constexpr
    const int diaDeNascimento = obterDiaDeNascimento();

    // A linha abaixo é "substituída" durante a compilação pela linha
    // de número 15.
    std::cout << "O dia de nascimento é: " << diaDeNascimento << std::endl;
    std::cout << "O dia de nascimento é: " << 10 << std::endl;  
    return 0;
}

Digamos, então, que se deseja utilizar uma variável constante em algum lugar onde se espera uma expressão constante. Como vimos nos dois exemplos anteriores, uma variável const em C++ pode ser uma constexpr ou não, a depender do valor que foi utilizado para inicializá-la: em const double distanciaSurpresa = obtemDistancia(), distanciaSurpresa não é uma expressão constante, pois o retorno da função obtemDistancia (que eu não defini, mas suponhamos) não é constexpr; em const int diaDeNascimento = obtemDiaDeNascimento(), por sua vez, o retorno da função de inicialização é constexpr, o que implica diaDeNascimento também ser constexpr. Assim, temos dois casos similares (de inicialização de variável constante através do valor de retorno de uma função) em que um deles resulta em uma variável que se comporta como expressão constante e o outro não. E se precisássemos nos certificar de que a variável inicializada seria equivalente a uma expressão constante? É para isso que serve o modificador constexpr (respondendo à pergunta do parágrafo anterior). Se quiserem uma outra perspectiva sobre o tema, consultem o artigo do CppReference sobre o assunto.

O modificador constexpr, utilizado na declaração de uma variável, indica ao compilador que a variável deve ser inicializada com uma expressão constante (para que ela própria seja uma expressão constante). Se a variável for inicializada com um valor que não é constexpr, ocorrerá um erro durante a compilação. Veja a seguir exemplos de inicialização correta e incorreta de uma variável constexpr.

// OK
constexpr double taxaDeCambioBrlUsd = 5.58;

int garantiaDaCompraEmAnos = 2;
// Erro - o valor de 'garantiaDaCompraEmAnos' não pode ser usado em uma expressão constante.
// Ainda que garantiaDaCompraEmAnos seja inteiro e inicializado com um literal, ela não é uma
// variável constante.
constexpr int garantiaExtendidaEmAnos = garantiaDaCompraEmAnos + 5;
Best practices

É sempre uma boa idéia declarar como constexpr variáveis constantes cujo valor pode ser determinado durante a compilação do programa.

Próximos assuntos

Nos próximos artigos, tratarei de dois elementos importantes da maioria dos programas em C++, e sobre os quais o const tem uma grande influência: referências e ponteiros. Até mais!

Foto de perfil de Emanoel

Sou apaixonado por tecnologia, literatura e também filosofia. O cultivo dessas paixões ao longo da minha trajetória me inspiraram a compartilhar aquilo que aprendo com os outros da maneira mais clara que eu possa. Sou formado em Engenharia Elétrica no Brasil, e também sou engenheiro formado na França. Trabalho atualmente como programador C++ em uma multinacional francesa, uma das maiores empresas de TI do mundo.

Leave a Reply

Your email address will not be published. Required fields are marked *