Recently the C++ community promotes the use of the new standards to modernize the existing code base. However even before the release of the C++11 standard some known C++ experts like Andrei Alexandrescu, Scott Meyers and Herb Sutter promotes the uses of the generic programming and they qualify it as Modern C++ Design. Here’s what say Andrei Alexandrescu about the Modern C++ design:
Modern C++ Design defines and systematically uses generic components – highly flexible design artifacts that are mixable and matchable to obtain rich behaviors with a small, orthogonal body of code.
Three assertions are interesting in his point of view:
- Modern C++ Design defines and systematically uses generic components.
- Highly flexible design.
- Obtain rich behaviors with a small, orthogonal body of code.
Modernize your C++ code it’s not only about using the new standards but we can also use some generics programming best practices to improve our code base. Let’s first discover some easy steps to manually modernize our code base and in the second section we will explore how to automatically modernize it.
II – Modernize manually your source code
Let’s take as example an algorithm and try to modernize it. Algorithms are used for calculation, data processing, and automated reasoning. Programming them is not always an easy task and it depends on their complexity. In C++ many efforts was done to simplify their implementation and to make them more powerful.
Let’s try to modernize this implementation of the quick sort algorithm:
// The partition function
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;
}
// The quicksort recursive function
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);
}
}
After all, here are some common traits of algorithms:
- Using containers of an element kind and Iterating over them.
- Comparison between elements.
- And of course some treatments over elements.
In our implementation the container is a raw array of int, we iterate thought increment and decrement. We compare using “<” and “>”, and we have some treatments like swapping data.
Let’s try to improve each one of these traits:
Step1: Replace containers by iterators
Using not generic containers will force us to use a specific element kind. To apply the same algorithm to other types, we have to copy/paste the code. The generic containers resolve this issue, and permit to use any element kind, for example for our quick sort algorithm, we can use std::vector<T> as container instead of a raw array.
A raw array or an std::vector is just one possibility between many others to represent a set of elements, we can also apply the same algorithm to a linked list, a queue or whatever other container. For this needs the iterator is the best choice to abstract the container used.
An iterator is any object that, pointing to some element in a range of elements , has the ability to iterate through the elements of that range using a set of operators (with at least the increment (++) and dereference (*) operators). Iterators are classified into five categories depending on the functionality they implement: Input, Output, Forward, Bidirectional and random access.
In our algorithm we have to specify which kind of iterator to use. For that we have to detect which iterations are used. For the quick sort algo the increment and decrement iterations are applied. Therefore, a bidirectional iterator is needed. Using iterators we can define the method like this:
template< typename BidirectionalIterator > void quick_sort( BidirectionalIterator first, BidirectionalIterator last )
Step2: Make the comparator generic if possible
For some algorithms, the elements treated are not only numbers, they could be string or a class. In this case make the comparator generic will permit us to have a more generic algorithm.
The quick sort algorithm could also be applied to a list of strings, Therefore it’s better to have a generic comparator.
After using a generic comparator, the definition could be modified like this:
template< typename BidirectionalIterator, typename Compare > void quick_sort( BidirectionalIterator first, BidirectionalIterator last, Compare cmp )
Step3: Replace treatments by standard ones
Most algorithms use recurrent treatments like min, max and swap. for these operations it’s preferable to not reinvent the wheel, and uses the standard implementation existing in the <algorithm> header.
In our case we can use the swap method from the STL than creating our specific method.
std::iter_swap( pivot, left );
And here’s the modified result after these three steps:
#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 >() ); }
This implementation has the following advantages:
- Could be applied to many element kind.
- The container could be vector, set, list or whatever container with a biderectional iterator.
- It uses a well optimized and tested standard functions.
II- Automatic modernization
It’s interesting to detect automatically places where we can use some C++11/C++14/C++17 features and if possible change the code automatically. For such needs clang-tidy is a standalone tool used to automatically convert C++ code, written against old standards, to use features of the newest C++ standard where appropriate.
Here are some places detected by clang-tidy where we can modernize the code:
- Override: Detect places where you can add the override specifier to member functions that override a virtual function in a base class and that don’t already have the specifier
- Loop Convert: Detect loops like for(…; …; …) to replace them with the new range-based loops in C++11 and give you the new range loop expression to use.
- Pass-By-Value: Detect const-ref parameters that would benefit from using the pass-by-value idiom.
- auto_ptr: Detect the uses of the deprecated std::auto_ptr to replace by std::unique_ptr.
- auto specifier: Detect places where to use the auto type specifier in variable declarations.
- nullptr: Detect null literals to be replaced by nullptr where applicable.
- std::bind:The check finds uses of
std::bind
and replaces simple uses with lambdas. Lambdas will use value-capture where required. - Deprecated headers:Some headers from C library were deprecated in C++ and are no longer welcome in C++ codebases. Some have no effect in C++. For more details refer to the C++ 14 Standard [depr.c.headers] section.
- std::shared_ptr: This check finds the creation of
std::shared_ptr
objects by explicitly calling the constructor and anew
expression, and replaces it with a call tostd::make_shared
. - std::unique_ptr: This check finds the creation of
std::unique_ptr
objects by explicitly calling the constructor and anew
expression, and replaces it with a call tostd::make_unique
, introduced in C++14. - raw string literals: This check selectively replaces string literals containing escaped characters with raw string literals.
Developers who use Clang could easily use the clang-tidy tool. However for Visual C++ developers and other compilers you can use CppDepend which integrate clang-tidy.