Llamamos su atención sobre la traducción de un breve artículo práctico sobre cómo lidiar con el legado redundante en código C ++. Esperamos que sea interesante.
Recientemente, la comunidad de C ++ ha estado promoviendo activamente el uso de nuevos estándares y la modernización de la base de código existente. Sin embargo, incluso antes de que se lanzara el estándar C ++ 11, reconocidos expertos en C ++ como Andre Alexandrescu, Scott Myers y Herb Sutter promovieron la programación C ++ genérica, que calificaron como "diseño C ++ moderno". André Alexandrescu lo expresó de esta manera:
El diseño moderno de C ++ define y utiliza sistemáticamente componentes genéricos: artefactos de diseño altamente flexibles que se pueden mezclar y combinar para producir comportamientos enriquecidos en un fragmento de código pequeño y ortogonal.
Tres afirmaciones son interesantes en esta tesis:
- El diseño moderno de C ++ define y utiliza sistemáticamente componentes genéricos .
- Diseño muy flexible .
- Obtenga comportamientos enriquecidos con un fragmento de código pequeño y ortogonal .
La modernización del código escrito en C ++ no se limita a la introducción de nuevos estándares, sino que también implica el uso de las mejores prácticas que se aplican en cualquier lenguaje de programación para ayudar a mejorar la base del código. Primero, analicemos algunos pasos simples para actualizar manualmente su base de código. En la tercera sección, hablaremos sobre las actualizaciones automáticas de código.
Actualización manual del código fuente
Tomemos un algoritmo como ejemplo e intentemos modernizarlo. Los algoritmos se utilizan para cálculos, procesamiento de datos y derivación automática de conclusiones. Programar un algoritmo es a veces una tarea no trivial y depende de su complejidad. En C ++, se realizan esfuerzos importantes para simplificar la implementación y aumentar el poder de los algoritmos.
Intentemos modernizar esta implementación del algoritmo de ordenación rápida:
//
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);
}
}
Después de todo, todos los algoritmos tienen ciertas cosas en común:
- Usar un contenedor para elementos de un cierto tipo e iterar sobre ellos.
- Comparación de elementos
- Algunas operaciones sobre elementos
En nuestra implementación, el contenedor es una matriz sin procesar de enteros, y iteramos sobre las operaciones de incremento y decremento en uno. La comparación se realiza utilizando
“<”
y
“>”
, y también realizamos algunas operaciones sobre los datos, por ejemplo, los intercambiamos.
Intentemos mejorar cada una de estas características del algoritmo:
Paso 1: cambio de contenedores a iteradores
Si abandonamos los contenedores genéricos, entonces nos veremos obligados a usar solo elementos de cierto tipo. Para aplicar el mismo algoritmo a otros tipos, tendremos que copiar y pegar el código. Los contenedores genéricos resuelven este problema y te permiten utilizar cualquier tipo de elemento. Por ejemplo, en un algoritmo de ordenación rápida, puede usarlo
std::vector<T>
como contenedor en lugar de una matriz sin formato.
Una matriz sin formato o
std::vector
es solo una de una variedad de opciones para representar muchos elementos. El mismo algoritmo se aplica a una lista vinculada, una cola o cualquier otro contenedor. Cuando se trabaja con un iterador, es mejor abstraer el contenedor utilizado.
Un iterador es cualquier objeto que, apuntando a un elemento en un cierto rango, puede iterar sobre todos los elementos del rango dado usando un conjunto de operadores (que incluye al menos el incremento por un operador (++) y el operador de desreferencia (*)). Los iteradores se dividen en cinco categorías según la función que realizan: entrada, salida, iterador unidireccional, iterador bidireccional y acceso aleatorio.
En nuestro algoritmo, tenemos que especificar qué iterador usaremos. Para hacer esto, necesitamos identificar qué iteraciones estamos usando. El algoritmo de clasificación rápida usa una iteración de incremento en uno y decremento en uno. Por tanto, necesitamos un iterador bidireccional. Con los iteradores, puede definir un método como este:
template< typename BidirectionalIterator >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last )
Paso 2: generalice el comparador, si es posible
Algunos algoritmos tienen que procesar no solo números, sino, por ejemplo, una cadena o una clase. En este caso, debe generalizar el comparador; esto nos permitirá lograr una mayor generalización de todo el algoritmo.
El algoritmo de clasificación rápida también se puede aplicar a una lista de cadenas. En consecuencia, un comparador generalizado es más adecuado para nosotros.
Usando el comparador genérico, puede modificar la definición de esta manera:
template< typename BidirectionalIterator, typename Compare >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last, Compare cmp )
Etapa 3: Reemplazo de operaciones existentes con operaciones estándar
La mayoría de los algoritmos utilizan operaciones repetitivas como
min
,
max
y
swap
. Al realizar tales operaciones, es mejor no reinventar la rueda y usar la implementación estándar que existe en el encabezado
<algorithm>
.
En nuestro caso, podemos utilizar el método de intercambio de la biblioteca estándar STL en lugar de crear nuestro propio método.
std::iter_swap( pivot, left );
Y aquí está el resultado modificado después de estos tres pasos:
#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 implementación tiene las siguientes ventajas:
- Aplicable a todo tipo de elementos.
- El contenedor puede ser un vector, conjunto, lista o cualquier otro provisto de un iterador bidireccional.
- Esta implementación utiliza funciones estándar optimizadas y probadas.
Actualización automática
Es interesante identificar automáticamente los lugares donde se pueden utilizar determinadas funciones de C ++ 11 / C ++ 14 / C ++ 17 y, si las condiciones son favorables, cambiar automáticamente el código. Para tales fines, existe una herramienta clang-tidy con todas las funciones que se utiliza para transformar automáticamente el código C ++ escrito de acuerdo con los estándares antiguos. Después de esta transformación, el código usa características de estándares más nuevos cuando corresponde.
Aquí hay algunas áreas donde clang-tidy sugiere actualizaciones de código:
- Anulación: busque lugares donde pueda agregar un puntero de anulación para una función de instancia que anula una función virtual en la clase base sin tener ya una
- :
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.- : , , .
Los desarrolladores que dominan Clang pueden aprender fácilmente a utilizar la herramienta clang-tidy. Pero al trabajar con Visual C ++, así como con otros compiladores, puede usar CppDepend , que incluye clang-tidy.