Quando usar o typedef?


14

Estou um pouco confuso sobre se e quando devo usar typedef em C ++. Eu sinto que é um ato de equilíbrio entre legibilidade e clareza.

Aqui está um exemplo de código sem nenhum typedefs:

int sum(std::vector<int>::const_iterator first, 
        std::vector<int>::const_iterator last)
{
    static std::map<std::tuple<std::vector<int>::const_iterator,
                               std::vector<int>::const_iterator>,
                    int> lookup_table;

    std::map<std::tuple<std::vector<int>::const_iterator,
                        std::vector<int>::const_iterator>, int>::iterator lookup_it =
        lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

OMI muito feio. Então, adicionarei alguns typedefs dentro da função para torná-la mais agradável:

int sum(std::vector<int>::const_iterator first, 
        std::vector<int>::const_iterator last)
{
    typedef std::tuple<std::vector<int>::const_iterator,
                       std::vector<int>::const_iterator> Lookup_key;
    typedef std::map<Lookup_key, int> Lookup_table;

    static Lookup_table lookup_table;

    Lookup_table::iterator lookup_it = lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

O código ainda é um pouco desajeitado, mas me livrei da maioria dos materiais de pesadelo. Mas ainda existem os iteradores de vetor int, essa variante se livra deles:

typedef std::vector<int>::const_iterator Input_iterator;

int sum(Input_iterator first, Input_iterator last)
{
    typedef std::tuple<Input_iterator, Input_iterator> Lookup_key;
    typedef std::map<Lookup_key, int> Lookup_table;

    static Lookup_table lookup_table;

    Lookup_table::iterator lookup_it = lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

Parece limpo, mas ainda é legível?

Quando devo usar um typedef? Assim que eu tiver um tipo de pesadelo? Assim que ocorre mais de uma vez? Onde devo colocá-los? Devo usá-los em assinaturas de funções ou mantê-los na implementação?


1
Não duplicado, mas algo relacionado à minha pergunta programmers.stackexchange.com/questions/130679/...
— c0da

typedef Input_iterator std::vector<int>::const_iterator;está ao contrário
— por Johansson

1
Há uma diferença entre legibilidade e clareza?
— 711 Neil

Quando #definenão é bom o suficiente.
— Thomas Eding

Respostas:


6

Seu último exemplo é muito legível, mas depende de onde você define o typedef. Typedefs de escopo local (como no seu segundo exemplo) são IMVHO quase sempre uma vitória.

Ainda gosto do seu terceiro exemplo, mas você pode pensar na nomeação e fornecer aos iteradores nomes que informam a intenção do contêiner.

Outra opção seria transformar um modelo em sua função, para que também funcione com diferentes contêineres. Ao longo das linhas de

template <typename Input_iterator> ... sum(Input_iterator first, Input_iterator last) 

o que também está muito no espírito do STL.


2

Pense em a typedefcomo a declaração de variável equivalente a uma função: está lá para que você ...

  • ... não precisa se repetir ao reutilizar o mesmo tipo (como seus dois primeiros exemplos).
  • ... pode ocultar os detalhes sangrentos do tipo para que eles nem sempre sejam exibidos.
  • ... verifique se as alterações no tipo são refletidas em todos os lugares em que são usadas.

Pessoalmente, olho para cima se tiver que ler nomes de tipos longos std::vector<int>::const_iteratorrepetidamente.

Seu terceiro exemplo não se repete desnecessariamente e é mais fácil de ler.


1

typedefdeclarações servem essencialmente ao mesmo propósito que o encapsulamento. Por esse motivo, eles quase sempre se encaixam melhor em um arquivo de cabeçalho, seguindo as mesmas convenções de nomenclatura que suas classes, porque:

  • Se você precisar typedef, é provável que os chamadores também façam isso, especialmente no exemplo em que ele é usado nos argumentos.
  • Se você precisar alterar o tipo por qualquer motivo, inclusive substituindo-o por sua própria classe, precisará fazê-lo em um só lugar.
  • Isso torna você menos propenso a erros devido à escrita repetida de tipos complexos.
  • Oculta detalhes de implementação desnecessários.

Como um aparte, seu código de memorização seria muito mais limpo se você o abstraísse ainda mais, como:

if (lookup_table.exists(first, last))
    return lookup_table.get(first, last);

Sua sugestão pode parecer mais limpa, mas perde tempo fazendo a pesquisa duas vezes.
— Derek Ledbetter

Sim, isso foi uma troca intencional. No entanto, existem maneiras de fazer isso com uma única pesquisa quase tão limpa, principalmente se você não estiver preocupado com a segurança do thread.
— Karl Bielefeldt
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.