typedefs e #defines

32

Definitivamente, todos nós usamos typedefs #definees uma vez ou outra. Hoje, enquanto trabalhava com eles, comecei a pensar em algo.

Considere as 2 situações abaixo para usar o inttipo de dados com outro nome:

typedef int MYINTEGER

e

#define MYINTEGER int

Como na situação acima, em muitas situações, podemos muito bem realizar algo usando #define e também o mesmo usando typedef, embora as maneiras pelas quais fazemos o mesmo possam ser bem diferentes. #define também pode executar ações MACRO que um typedef não pode.

Embora a razão básica para usá-los seja diferente, qual é o nível de trabalho deles? Quando um deve ser preferido em relação ao outro, quando ambos podem ser usados? Além disso, é garantido que um é mais rápido que o outro em que situações? (por exemplo, #define é diretiva de pré-processador, portanto, tudo é feito muito mais cedo do que na compilação ou no tempo de execução).

c0da
fonte
8
Use #define para o que eles foram projetados. Compilação condicional com base em propriedades que você não pode saber ao escrever o código (como OS / Compiler). Use construções de linguagem de outra forma.
Martin York

Respostas:

67

A typedefé geralmente preferido, a menos que haja algum motivo estranho para você precisar especificamente de uma macro.

macros fazem substituição textual, o que pode causar considerável violência à semântica do código. Por exemplo, dado:

#define MYINTEGER int

você poderia escrever legalmente:

short MYINTEGER x = 42;

porque se short MYINTEGERexpande para short int.

Por outro lado, com um typedef:

typedef int MYINTEGER:

o nome MYINTEGERé outro nome para o tipo int , não uma substituição textual para a palavra-chave "int".

As coisas pioram com os tipos mais complicados. Por exemplo, dado o seguinte:

typedef char *char_ptr;
char_ptr a, b;
#define CHAR_PTR char*
CHAR_PTR c, d;

a, bE csão todos os ponteiros, mas dé um char, porque a última linha se expande para:

char* c, d;

que é equivalente a

char *c;
char d;

(Typedefs para tipos de ponteiros geralmente não são uma boa ideia, mas isso ilustra o ponto.)

Outro caso estranho:

#define DWORD long
DWORD double x;     /* Huh? */
Keith Thompson
fonte
1
Ponteiros para culpar novamente! :) Algum outro exemplo além de ponteiros onde não é aconselhável usar essas macros?
C0da
2
Não que eu jamais usar uma macro no lugar de um typedef, mas a maneira correta de construir uma macro que faz algo com o que se segue é a utilização de argumentos: #define CHAR_PTR(x) char *x. Pelo menos isso fará com que o compilador kvetch se usado incorretamente.
Blrfl
1
Não esqueça:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
Jon Purdy
3
Define também fazer mensagens de erro incompreensível
Thomas Bonini
Um exemplo da dor que o desvio de macros pode causar (embora não diretamente relevante para macros vs. typedefs): github.com/Keith-S-Thompson/42
Keith Thompson
15

De longe, o maior problema das macros é que elas não têm escopo definido. Isso por si só garante o uso de typedef. Além disso, é mais claro semanticamente. Quando alguém que lê seu código vê uma definição, ele não saberá do que se trata até que leia e compreenda toda a macro. Um typedef diz ao leitor que um nome de tipo será definido. (Devo mencionar que estou falando sobre C ++. Não tenho certeza sobre o escopo de typedef para C, mas acho que é semelhante).

Tamás Szelei
fonte
Mas, como mostrei no exemplo fornecido na pergunta, um typedef e #define usam o mesmo número de palavras! Além disso, essas três palavras de uma macro não causarão confusão, pois as palavras são as mesmas, mas são reorganizadas. :)
c0da
2
Sim, mas não se trata de falta. Uma macro pode fazer um zilhão de outras coisas além da digitação. Mas, pessoalmente, acho que o escopo é o mais importante.
Tamás Szelei 18/01/12
2
@ c0da - você não deve usar macros para typedefs, mas também typedefs para typedefs. O efeito real da macro pode ser muito diferente, conforme ilustrado por várias respostas.
Joris Timmermans
15

O código fonte é escrito principalmente para colegas desenvolvedores. Os computadores usam uma versão compilada.

Nesta perspectiva, typedefcarrega um significado que #definenão.

mouviciel
fonte
6

A resposta de Keith Thompson é muito boa e, juntamente com os pontos adicionais de Tamás Szelei sobre o escopo, devem fornecer todo o histórico necessário.

Você deve sempre considerar as macros o último recurso dos desesperados. Há certas coisas que você só pode fazer com macros. Mesmo assim, você deve estar pensando muito, se realmente quiser. A quantidade de problemas na depuração que pode ser causada por macros sutilmente quebradas é considerável e você terá que procurar em arquivos pré-processados. Vale a pena fazer isso apenas uma vez, para ter uma idéia do escopo do problema em C ++ - apenas o tamanho do arquivo pré-processado pode ser uma surpresa.

Joris Timmermans
fonte
5

Além de todas as observações válidas sobre o escopo e a substituição de texto já fornecidas, #define também não é totalmente compatível com o typedef! Ou seja, quando se trata de ponteiros de função.

typedef void(*fptr_t)(void);

Isso declara um tipo fptr_tque é um ponteiro para uma função do tipo void func(void).

Você não pode declarar esse tipo com uma macro. #define fptr_t void(*)(void)obviamente não vai funcionar. Você teria que escrever algo obscuro #define fptr_t(name) void(*name)(void), o que realmente não faz sentido na linguagem C, onde não temos construtores.

Os ponteiros de matriz também não podem ser declarados com #define: typedef int(*arr_ptr)[10];


E, embora C não tenha suporte a idiomas para segurança de tipo, vale a pena mencionar, outro caso em que typedef e #define não são compatíveis é quando você faz conversões de tipo suspeitas. A ferramenta do compilador e / ou analisador astático poderá fornecer avisos para essas conversões se você usar o typedef.


fonte
1
Ainda melhores são as combinações de ponteiros de função e de matriz, como char *(*(*foo())[])();( fooé uma função que retorna um ponteiro para uma matriz de ponteiros para funções que retornam o ponteiro para char).
John Bode
4

Use a ferramenta com a menor potência possível e a com mais avisos. #define é avaliado no pré-processador, você está sozinho por lá. typedef é avaliado pelo compilador. As verificações são feitas e o typedef pode definir apenas tipos, como o nome indica. Então, no seu exemplo, definitivamente escolha typedef.

Martin
fonte
2

Além dos argumentos normais contra define, como você escreveria essa função usando macros?

template <typename IterType>
typename IterType::value_type Sum(
    const IterType& begin, 
    const IterType& end, 
    const IterType::value_type& initialValue)
{
    typename IterType::value_type result = initialValue;
    for (IterType i = begin; i != end; ++i)
        result += i;

    return result;
}

....

vector<int> values;
int sum = Sum(values.begin(), values.end(), 0);

Obviamente, esse é um exemplo trivial, mas essa função pode somar qualquer sequência iterável direta de um tipo que implemente adição *. Typedefs usados ​​assim são um elemento importante da Programação Genérica .

* Acabei de escrever isso aqui, deixo de compilá-lo como um exercício para o leitor :-)

EDITAR:

Essa resposta parece estar causando muita confusão, então deixe-me extrair mais. Se você olhar dentro da definição de um vetor STL, verá algo semelhante ao seguinte:

template <typename ValueType, typename AllocatorType>
class vector
{
public:
    typedef ValueType value_type;
...
}

O uso de typedefs nos contêineres padrão permite que uma função genérica (como a que eu criei acima) faça referência a esses tipos. A função "Soma" é modelada no tipo de contêiner ( std::vector<int>), não no tipo mantido dentro do contêiner ( int). Sem typedef, não seria possível fazer referência a esse tipo interno.

Portanto, typedefs são centrais para o C ++ moderno e isso não é possível com macros.

Chris Pitman
fonte
A pergunta feita sobre macros vs typedef, não macros vs funções embutidas.
Ben Voigt
Claro, e isso só é possível com typedefs ... é isso que value_type é.
Chris Pitman
Este não é um typedef, é uma programação de modelos. Uma função ou functor não é um tipo, não importa quão genérico seja.
@Lundin Adicionei mais detalhes: A função Sum só é possível porque contêineres padrão usam typedefs para expor os tipos nos quais são modelados. Eu estou não dizer que Sum é um typedef. Estou dizendo que std :: vector <T> :: value_type é um typedef. Fique à vontade para procurar a origem de qualquer implementação de biblioteca padrão para verificar.
Chris Pitman
Justo. Talvez fosse melhor publicar apenas o segundo trecho.
1

typedefse encaixa na filosofia C ++: todas as verificações / declarações possíveis em tempo de compilação. #defineé apenas um truque de pré-processador que esconde muita semântica para o compilador. Você não deve se preocupar mais com o desempenho da compilação do que com a correção do código.

Quando você cria um novo tipo, está definindo uma nova "coisa" que o domínio do seu programa manipula. Portanto, você pode usar essa "coisa" para compor funções e classes e usar o compilador para seu benefício, para fazer verificações estáticas. De qualquer forma, como o C ++ é compatível com o C, há muitas conversões implícitas entre inte tipos baseados em int que não geram avisos. Portanto, você não recebe todo o poder das verificações estáticas neste caso. No entanto, você pode substituir o seu typedefpor um enumpara encontrar conversões implícitas. Por exemplo: se você tiver typedef int Age;, poderá substituir por enum Age { };e receberá todos os tipos de erros por conversões implícitas entre Agee int.

Outra coisa: o typedefpode estar dentro de um namespace.

dacap
fonte
1

Usar define em vez de typedefs é preocupante sob outro aspecto importante, a saber, o conceito de características de tipo. Considere classes diferentes (pense em contêineres padrão), que definem seus typedefs específicos. Você pode escrever código genérico consultando os typedefs. Exemplos disso incluem os requisitos gerais de contêiner (padrão c ++ 23.2.1 [container.requirements.general]) como

X::value_type
X::reference
X::difference_type
X::size_type

Tudo isso não é expressável de maneira genérica com uma macro porque não tem escopo definido.

Francesco
fonte
1

Não esqueça as implicações disso no seu depurador. Alguns depuradores não tratam #define muito bem. Brinque com os dois no depurador que você usa. Lembre-se de que você passará mais tempo lendo do que escrevendo.

anon
fonte
-2

"#define" substituirá o que você escreveu depois, typedef criará o tipo. Portanto, se você deseja ter um tipo de cliente - use typedef. Se você precisar de macros - use define.

Dainius
fonte
Eu simplesmente amo as pessoas que votam e nem se dão ao trabalho de escrever um comentário.
Dainius