sábado, 20 de agosto de 2011

A maldição das macros

Para ser direto: em C, as macros são úteis para definir constantes, definir rotinas e para compilação condicional. Em C++, somente a terceira situação é válida.

Vamos ao exemplo de uma macro válida em C++:

#define CONSOLE_DEBUG
float x = sin(y);

#if defined (CONSOLE_DEBUG )
printf(“Valor do seno de y: %f\n”, x );
#endif

E é isso. Não utilize em C++ macros para definir constantes ou rotinas. Se for programar em C, utilize-as com cuidado.

Vamos a exemplos de problemas com macros.

Queremos uma macro que calcule o cubo de um número. Ela pode ser facilmente definida como:

#define cubo( A ) A*A*A

Quando, no código, você fizer:

int y = cubo(3);

Seu código será substituído por:

int y = 3*3*3;

Alá! y vale 27! Funcionou perfeitamente!

Aí em outra parte do código você faz:

int x = 2;
int y = cubo( x + 1 );

Infelizmente o resultado de y será 7. Isso acontece porque seu código será substituído por:

int y = x + 1 * x + 1 * x + 1;

Sabemos desde criança que a multiplicação tem precedência sobre a soma, e por esta razão o resultado não foi o esperado.

Uma versão melhorada desta macro seria:

#define cubo( A ) (A)*(A)*(A)

Ok, agora o exemplo do x + 1 funciona, mas e o seguinte caso?

int y = 27 / cubo( 3 )

Esperamos que o resultado seja 1, mas infelizmente, como dizem, a estrada do inferno é pavimentada de boas intenções. Seu y valerá 81!
É simples:

27 / (3) * (3) * (3)

27/3 será executado primeiro, e o resultado disso será multiplicado por 3 duas vezes, chegando ao incrível valor de 81. Precisamos de mais parêntesis. Vamos lá:

#define cubo( A ) ( (A) * (A) * (A) )

E finalmente chegamos a macro perfeita! Bom, quase. Alguém pode fazer (e com certeza fará!) alguma coisa do tipo:

int x = 2;
int y = cubo( ++x );

Esperamos que y valha 27 no fim das contas, mas você já deve ter percebido a bosta aqui:

int y = ( ( ++x ) * ( ++x ) * ( ++x ) );

No final x valerá 5 e y valerá alguma coisa que conhecemos como undefined behavior. (http://renangreinert.blogspot.com/2011/08/x-x.html).

Infelizmente, se você programa em C, terá que conviver com este risco. Se você programa em C++, evite todos estes problemas utilizando funções inline em vez de macros. O exemplo acima seria melhor escrito na seguinte forma:

inline int cubo( int num )
{
    return num * num * num;
}

Quer que cubo valha para outros valores, como float, double, char? Utilize template:

template < typename T >
inline T cubo( T num )
{
    return num * num * num;
}

E assim ficamos todos felizes! =D

Outro exemplo de problemas com rotinas em macros são as que têm mais de 1 linha.

#define imprime \
printf( "1\n" ); \
printf( "2\n" ); \
printf( "3\n" );

for ( int i = 0; i < 10; ++i )
    imprime;

Note que apenas a primeira linha da macro (isto é, a que imprime 1) será executada 10 vezes. 2 e 3 serão impressos apenas uma vez, no fim do for.
Bom, é uma boa prática sempre fechar os fors, ifs, whiles, etc com chaves, mesmo que tenham apenas 1 linha, porém como muita gente não segue isso, é melhor fechar as suas macros também. Assim:

#define imprime \
{ \
printf( "1\n" ); \
printf( "2\n" ); \
printf( "3\n" ); \
}

Aí está um exemplo de uma macro sadia de múltiplas linhas em C (nem preciso mencionar que em C++ você deve utilizar funções inline, né?).

Ok, e os #defines que definem valores, e não rotinas?

Vamos ao exemplo:

#define MAX_VALUE 10

Esta abordagem possui vários problemas. O primeiro é que você não sabe o que é “10”. Pode ser um int, um char, um short ou até mesmo um float mal definido (a definição correta seria 10.0F).

Este problema pode ser facilmente resolvido com um cast:

#define MAX_VALUE (int)10

Melhor, mas ainda assim tem seus problemas. Suponha que seu programa calcula a pressão, a temperatura e a velocidade. MAX_VALUE é o valor máximo do quê realmente?
A única forma de saber é olhando a definição e torcer para que tenha algum comentário ou coisa do tipo.
Você pode desenvolver um padrão para definição das macros, algo do tipo PRESSAO_MAX_VALUE, VELOC_MAX_VALUE, TEMPERATURA_MAX_VALUE, etc, mas quando você trabalha com projetos muito grandes isso começa a encher o saco.

Outro problema com #defines é que se você definir MAX_VALUE em uma parte do código com 10 e em outra parte do código como sendo 20, seu compilador dará um warning, mas só. Como (infelizmente) muitas pessoas tem o hábito de ignorar warnings, isso será um problema.

Se você utilizar variáveis do tipo const em C++, você estará livre destes problemas.
No exemplo citado, você poderia ter:

class Pressao
{
public:
    static int MAX_VALUE = 10;
    ...
};

class Temperatura
{
public:
    static int MAX_VALUE = 30;
    ...
};

class Velocidade
{
public:
    static int MAX_VALUE = 100;
    ...
};

No código, você poderia utilizar:

int x = Velocidade::MAX_VALUE;
int y = Temperatura::MAX_VALUE;

e por aí vai.

Melhor, né?

É triste, mas sempre tem um porém. A versão atual do C++ só permite que tipos com valores inteiros (int, char, short...) sejam definidos na declaração dentro de classes. Assim, floats, doubles e strings estão de fora. A nova versão do C++ (a C++0x, se você ainda não ouviu falar é bom se atualizar) promete resolver este problema, mas enquanto ela não vem, vamos as alternativas.

Uma é definir as variáveis dentro do namespace. Algo do tipo:

namespace nsPressao
{
    const float MAX_VALUE = 10.5F;

    class Pressao
    {
        ...
    };
}

Outra alternativa seria declarar na classe e definir fora:

//Pressao.h
class Pressao
{
public:
    static const float MAX_VALUE;
};


e no começo do Pressao.cpp definir a variável:

//Pressao.cpp
const float Pressao::MAX_VALUE = 10.5F;

Pressao::Pressao()
...

E é isso. Espero ter convencido a todos de que macros em C++ não são uma boa opção! Abraço e bom fim de semana!



Nenhum comentário:

Postar um comentário