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?

futlib
fonte
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.

Fabio Fracassi
fonte
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.

Blrfl
fonte
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);
Karl Bielefeldt
fonte
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