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.