13.4.12

const correctness em C++: Substituição de valor

Uma das coisas que sempre me pegam quando vou ler um código C++ é pegar uma declaração desse tipo:

const Classe * point = new Classe();

No meu entender, a palavra-chave const sempre teve aquela função em C de declarar uma variável sem se utilizar de valores definidos no pré-processador
por exemplo, quando a gente usa um #define CONSTANTE. De fato,  o propósito
original de const foi exatamente substituir o #define.


const é uma palavra que em C++ foi ampliada em diversos usos e quero nos próximos posts ir relatando minha compreensão quanto a ela. Essa primeira parte é relativa a constantes de fato, substituição de valores (mais específicamente constant folding) e tipos de linkagem.




const para constantes


Quando você define um valor no pré-processador, o compilador não tem acesso a tipagem, logo corre-se o risco de ocorrer bugs sutis, muito difíceis de achar.


Por exemplo, em

#define BUFFER 100

BUFFER vai se comportar na maioria das vezes como uma variável qualquer. No entanto isso não é assegurado, sem falar no problema de tipagem, já que não se sabe o que foi definido, um float, double ou int. Agora, usando const,

const int buffer = 100;

pode-se usar buffer em qualquer lugar onde o compilador tem acesso. Com isso o compilador irá guardar o valor na tabela de simbolos e substituir na constante, caracterizando o constant folding. em que o compilador irá reduzir uma expressão de constante complicada para uma mais simples, apenas fazendo alguns cálculos em tempo de compilação.

const em cabeçalhos (headers)

Dar preferência a const em vez de #define nos leva a questionar o seu uso em arquivos de cabeçalho. const em C++ tem linkagem interna, por padrão. Isso significa que uma constante definida por ela é visível apenas no arquivo em que foi definida. Assim, deve-se sempre atribuir um valor a uma variável caracterizada como const, já que em linkagem interna não se pode apenas declarar valores, deve-se defini-las com algum valor.


Uma exceção a isso é o uso de extern, em que define explicitamente que uma variável tem linkagem externa e com isso terá um espaço alocado para armazenar o valor (normalmente os compiladores evitam armazenar consts em memória, deixando guardada a definição na tabela de símbolos).
extern const int buffer;
Com esse valor definido como linkagem externa, outros arquivos terão visibilidade da variável const. 


O motivo de const ser definida como linkagem interna por padrão é que o linkador não irá tentar interligar definições de const em vários arquivos, evitando colisões. Se fosse externa, memória seria alocada, gerando conflitos de nomes no linkador.

consts seguros

Quando um valor é produzido em tempo de compilação e você sabe que esse valor não irá mudar ao longo do programa, o const para previne possíveis alterações nesse valor.  No exemplo a seguir, em que define-se vários tipos de consts, a variável const 'c' na função main() recebe um valor de entrada em sua definição, onde não será mais possível altera-la ao longo da função, sendo assim caracterizada como uma constante de tempo de execução.

#include <iostream>
using namespace std;

const int i = 100; // constante tipica 
/* 
 * por ser definida a partir de outra constante, j irá ser 
 * armazenada na tabela de simbolos, como uma constante típica
 */
const int j = i + 10;

//essa linha força a alocação de j por requerer seu endereço 
long address = (long) &j; 

//como o compilador reconhece j como const, pode-se utiliza-lo
//para definir o tamanho de buf 
char buf[j + 10] ; 

int main(){ 
    cout << "insira um caractere "; 

// como o valor da constante 'c' é definida em tempo de execução 
// será necessário alocar valor na memória (como em C), ao invés 
// de armazenar numa tabela de símbolos. 
const char c = cin.get(); 
const char c2 = c + 'a'; 
cout << c2; 

}

Na prática, se um valor não deve mudar deve-se declará-lo com um const. Desta forma ocorre melhora de desempenho na prevenção leitura e escrita na memória, precisando apenas fazer o constant folding do compilador.

Diferenças em relação ao C

Em C, const tem linkagem externa por padrão, daí sempre ocupa espaço de memória. Trocando em miúdos, uma declaração const é variável  comum que não pode ser modificada ( ao contrário de C++ em que  é guardada na tabela de símbolos e ocorre constant folding). Com isso, uma declaração do tipo:

const int bufsize = 100;
char buffer[bufsize];

não é permitida em C. Por ser armazenada em algum lugar, bufsize teria que ser tratada como constante de tempo de compilação, o que não ocorre em C. Por isso o compilador não irá saber o valor de bufsize, já que estará em memória e não será acessivel em tempo de compilação. A alternativa seria

const int bufsize;

Já em C++, a declaração acima é invalida, pois está declarando uma variável const, mas não está definindo: toda variável declarada como const deve ser inicializada em C++, devido a linkagem interna. Para apenas declarar esta variável, em C++ deve-se usar extern,


extern const int bufsize;

indicando ao compilador que a linkagem foi definida como externa.

Como em C++ não se cria espaço para armazenamento para const, a visibilidade é apenas no arquivo declarado. Por exemplo, se um const é definido fora de qualquer função/método seu escopo é o arquivo declarado. Isso difere de outras variáveis de C++ e de const em C. Assim, se for desejável ter acesso em outros arquivos de uma constante definida, deve-se declará-la como extern,

extern const int y = 20;

isso força alocação de y, mas previne o constant folding de uma constante em C++.

Nenhum comentário:

Postar um comentário