Como é fácil modernizar o código C ++

Olá, Habr!



Chamamos sua atenção para a tradução de um pequeno artigo prático sobre como lidar com legado redundante em código C ++. Esperamos que seja interessante.



Recentemente, a comunidade C ++ tem promovido ativamente o uso de novos padrões e a modernização da base de código existente. No entanto, mesmo antes do lançamento do padrão C ++ 11, renomados especialistas em C ++, como Andre Alexandrescu, Scott Myers e Herb Sutter, promoveram a programação C ++ genérica, que eles qualificaram como "design C ++ moderno". André Alexandrescu colocou desta forma:



O design C ++ moderno define e usa sistematicamente componentes genéricos - artefatos de design altamente flexíveis que podem ser misturados e combinados para produzir comportamentos ricos em um pequeno trecho ortogonal de código.


Três afirmações são interessantes nesta tese:



  • O design moderno do C ++ define e usa sistematicamente componentes genéricos .
  • Design muito flexível .
  • Obtenha comportamentos ricos com um pequeno trecho de código ortogonal .


A modernização do código escrito em C ++ não se limita à introdução de novos padrões, mas também envolve o uso das melhores práticas que se aplicam a qualquer linguagem de programação para ajudar a melhorar a base do código. Primeiro, vamos discutir algumas etapas simples para atualizar manualmente sua base de código. Na terceira seção, falaremos sobre atualizações automáticas de código.



Atualização manual do código-fonte



Vamos pegar um algoritmo como exemplo e tentar modernizá-lo. Algoritmos são usados ​​para cálculos, processamento de dados e derivação automática de conclusões. Programar um algoritmo às vezes é uma tarefa não trivial e depende de sua complexidade. Em C ++, esforços significativos são feitos para simplificar a implementação e aumentar o poder dos algoritmos.

Vamos tentar modernizar esta implementação do algoritmo quicksort:



//  
int partition(int* input,int p,int r){
        int pivot = input[r];
        while( p < r ){
                 while( input[p]< pivot )
                     p++;
                 while( input[r]> pivot )
                    r--;
                if( input[p]== input[r])
                    p++;
                elseif( p < r ){
                     int tmp = input[p];
                     input[p]= input[r];
                     input[r]= tmp;
                }
        }
         return r;
}
//    
void quicksort(int* input,int p,int r){
        if( p < r ){
              int j = partition(input, p, r);        
              quicksort(input, p, j-1);
              quicksort(input, j+1, r);
        }
}

      
      





Afinal, todos os algoritmos têm certas coisas em comum:



  • Usando um contêiner para elementos de um determinado tipo e iterando sobre eles.
  • Comparação de elementos
  • Algumas operações em elementos


Em nossa implementação, o contêiner é uma matriz bruta de inteiros, e iteramos as operações de incremento e decremento em um. A comparação é realizada usando “<”



e “>”



, e também realizamos algumas operações nos dados, por exemplo, trocamos os dados.



Vamos tentar melhorar cada um desses recursos do algoritmo:



Etapa 1: Mudando Containers para Iteradores



Se abandonarmos os containers genéricos, seremos forçados a usar apenas elementos de um determinado tipo. Para aplicar o mesmo algoritmo a outros tipos, teremos que copiar e colar o código. Os recipientes genéricos resolvem este problema e permitem que você use qualquer tipo de elemento. Por exemplo, em um algoritmo de classificação rápida, você pode usá-lo std::vector<T>



como um contêiner em vez de uma matriz bruta.



Matriz bruta ou std::vector



é apenas uma de uma variedade de opções para representar muitos elementos. O mesmo algoritmo se aplica a uma lista vinculada, uma fila ou qualquer outro contêiner. Ao trabalhar com um iterador, é melhor abstrair o contêiner usado.



Um iterador é qualquer objeto que, apontando para um elemento em um determinado intervalo, pode iterar sobre todos os elementos de um determinado intervalo usando um conjunto de operadores (que inclui pelo menos o incremento de um operador (++) e o operador de desreferência (*)). Os iteradores se enquadram em cinco categorias com base na função que executam: entrada, saída, iterador unilateral, iterador bidirecional e acesso aleatório.



Em nosso algoritmo, temos que especificar qual iterador usaremos. Para fazer isso, precisamos identificar quais iterações estamos usando. O algoritmo quicksort usa uma iteração de incremento de um e decremento de um. Portanto, precisamos de um iterador bidirecional. Com iteradores, você pode definir um método como este:



template< typename BidirectionalIterator >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last )
      
      





Etapa 2: generalize o comparador, se possível



Alguns algoritmos precisam processar não apenas números, mas, por exemplo, uma string ou uma classe. Nesse caso, você precisa tornar o comparador generalizado; isso nos permitirá alcançar uma maior generalização de todo o algoritmo.



O algoritmo de classificação rápida também pode ser aplicado a uma lista de strings. Conseqüentemente, um comparador generalizado é mais adequado para nós.



Usando o comparador generalizado, você pode modificar a definição assim:



template< typename BidirectionalIterator, typename Compare >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last, Compare cmp )
      
      





Etapa 3: Substituir as operações existentes por outras padrão



A maioria dos algoritmos usa operações repetitivas como min



, max



e swap



. Ao realizar tais operações, é melhor não reinventar a roda e usar a implementação padrão que existe no cabeçalho <algorithm>



.



Em nosso caso, podemos usar o método de troca da biblioteca padrão STL em vez de criar nosso próprio método.



std::iter_swap( pivot, left );
      
      





E aqui está o resultado modificado após essas três etapas:



#include <functional>
#include <algorithm>
#include <iterator>
 
template< typename BidirectionalIterator, typename Compare >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last, Compare cmp ) {
    if( first != last ) {
        BidirectionalIterator left  = first;
        BidirectionalIterator right = last;
        BidirectionalIterator pivot = left++;
 
        while( left != right ) {
            if( cmp( *left, *pivot ) ) {
                ++left;
            } else {
                while( (left != right) && cmp( *pivot, *right ) )
                    --right;
                std::iter_swap( left, right );
            }
        }
 
        --left;
        std::iter_swap( pivot, left );
 
        quick_sort( first, left, cmp );
        quick_sort( right, last, cmp );
    }
}
 
template< typename BidirectionalIterator >
    inline void quick_sort( BidirectionalIterator first, BidirectionalIterator last ) {
        quick_sort( first, last,
                std::less_equal< typename std::iterator_traits< BidirectionalIterator >::value_type >()
                );
    }
      
      





Esta implementação tem as seguintes vantagens:



  • Aplicável a todos os tipos de elementos.
  • O contêiner pode ser um vetor, conjunto, lista ou qualquer outro fornecido com um iterador bidirecional.
  • Esta implementação usa funções padrão otimizadas e testadas.


Atualização automática



É interessante identificar automaticamente os locais onde determinados recursos do C ++ 11 / C ++ 14 / C ++ 17 podem ser usados ​​e, se as condições forem favoráveis, alterar automaticamente o código. Para tais propósitos, existe uma ferramenta clang-tidy cheia de recursos usada para transformar automaticamente o código C ++ escrito de acordo com padrões antigos. Após essa conversão, o código usa recursos de padrões mais novos quando apropriado.



Aqui estão algumas áreas em que o clang-tidy sugere atualizações de código:



  • Substituindo: Encontre lugares onde você pode adicionar um ponteiro de substituição para uma função de instância que substitui uma função virtual na classe base sem já ter uma
  • : for(…; …; …)



    , , , .
  • : const-ref



    , .
  • auto_ptr



    : std::auto_ptr



    std::unique_ptr



    .
  • -: , auto



    .
  • nullptr



    : , nullptr



    , .
  • std::bind



    : std::bind , , . , , .
  • : C C++ . C++. C++ 14 [depr.c.headers].
  • std::shared_ptr



    : std::shared_ptr



    new



    , std::make_shared



    .
  • std::unique_ptr



    : std::shared_ptr



    new



    , std::make_unique



    , C++14.
  • : , , .


Os desenvolvedores que dominam o Clang podem aprender facilmente a usar a ferramenta clang-tidy. Mas ao trabalhar com Visual C ++, bem como outros compiladores, você pode usar CppDepend , que inclui clang-tidy.



All Articles