OTB++11
Modern C++
I have just recently had a look at the last C++ ISO standard (known as C++11). I was not aware of all the fancy features that have been included in the language.
Some of the most interesting features are:
- auto,
- range-based
for
loops, - general constant expressions,
- uniform initialisation,
- type aliases,
- decltype,
- variadic templates,
- type safe threading,
- user-defined literals,
- move semantics
There are also new containers and algorithms in the STL.
During the end of the year break, I spent some time reading and watching on line presentations about the new C++. I recommend watching Bjarne Stroustrup's keynote at Going Native 2012 (link) and Scott Meyer's talk at Going Native 2013 where he even anticipates some C++14 features (link).
Stroustrup introduces his talk by saying:
I use C++11 freely. Examples include auto, general constant expressions, uniform initialisation, type aliases, type safe threading, and user-defined literals. C++11 features are only just starting to appear in production compilers, so some of my suggestions are conjecture. Developing a "modern style," however, is essential if we don't want to maintain newly-written 1970s and 1980s style code in 2020.
After listening at him, I realised that the most easy to use features alone could make C++ programming much more fun and simple. Since I have spent some time in the last 2 years flirting with functional and dynamic typed (or at least with type inference) languages (Scheme, Clojure, Python, SML), I wanted to see how using C++11 affects the style of OTB code.
As usual, GCC has been the first compiler to make available these new
features (closely followed by Clang). C++11 is available in recent
versions of GCC using -std=c++11
. So I recompiled OTB with this flag
and started modifying some of the Software Guide examples using C++11
features.
All in all, I can say that these features make OTB code shorter, clearer and maybe safer. I report some examples here.
Examples
auto
The auto
keyword allows to declare variables without explicitly
stating their type and puts this burden on the compiler. This is not
dynamic typing, this is type inference and nothing changes in terms of
what the compiler generates. So you can write:
auto x = sqrt(2.0);
and the type of x
will be deduced from the return value type of
sqrt
. You can also do this:
auto x=0; auto y=0.0;
and x
will be of type int
and y
will be of type double
. If you
want to force other types, you have to use the classical declarations.
How to benefit from auto
when using OTB? A common pattern in OTB
code is to instantiate a smart pointer using the New()
class
method. Sometimes, we just want to create one object of a given type
and we do this:
typedef otb::ImageFileReader<ImageType> ReaderType; ReaderType::Pointer reader = ReaderType::New(); typedef otb::ImageFileWriter<ImageType> WriterType; WriterType::Pointer writer = WriterType::New();
in order to create a reader and a writer. That is, since type names
are long we define a type using typedef
and then we use it twice,
once for Pointer
and once for New
.
Using auto, we can reduce the boilerplate like this:
auto reader = otb::ImageFileReader<ImageType>::New(); auto writer = otb::ImageFileWriter<ImageType>::New();
The same applies for filters, for which usually we only use one instance:
typedef itk::RescaleIntensityImageFilter<ImageType, OutputImageType> RescalerType; RescalerType::Pointer rescaler = RescalerType::New();
becomes
auto rescaler = itk::RescaleIntensityImageFilter<ImageType, OutputImageType>::New();
which is once again much shorter.
Another example in a for
loop:
for (PolygonListType::Iterator it = polygonList->Begin(); it != polygonList->End(); ++it) { DataNodeType::Pointer newPolygon = DataNodeType::New(); newPolygon->SetPolygonExteriorRing(it.Get()); tree->Add(newPolygon, multiPolygon); }
becomes
for (auto it = polygonList->Begin(); it != polygonList->End(); ++it) { auto newPolygon = DataNodeType::New(); newPolygon->SetPolygonExteriorRing(it.Get()); tree->Add(newPolygon, multiPolygon); }
using
The using
keyword allows to define template aliases. Whenever we
write this:
typedef otb::Image<unsigned int, 2> ImageType; typedef otb::VectorImage<unsigned int, 2> VectorImageType; typedef otb::Image<double, 2> DoubleImageType; typedef otb::VectorImage<double, 2> DoubleVectorImageType;
we could do this:
using ImageType = otb::Image<unsigned int, 2>; using VectorImageType = otb::VectorImage<unsigned int, 2>; using DoubleImageType = otb::Image<double, 2>; using DoubleVectorImageType = otb::VectorImage<double, 2>;
For people like me who have always found counterintuitive the
order of the types in a typedef
, this is much clear. I know that the
code is not shorter, but for a C++ beginner, this should be much
clearer.
One cool thing that using
allows and that typedef
doesn't is
partial template specialisation1:
template<typename T> using Image2D = otb::Image<T, 2>; auto im = Image2D<int>::New();
decltype
Sometimes, we don't want to mentally track the type names, but we know
that to objects should have the same type. decltype
allows to
declare the type of one object by using the name of another object:
ImageType::IndexType start = {0, 0}; decltype(start) start2 = {0, 0};
In OTB, at first, I didn't see a way to use decltype
on smart
pointers and templates, since we usually manipulate
ObjectType::Pointer
, but the template parameters are simply
ObjectType
. Fortunately, ITK designers declared an ObjectType
in
the SmartPointer
class which is an alias for the type of the pointed
object.
I found an example where this is useful (the orthofusion example in the Tutorial chapter of the Software Guide).
The original code defines a type for a projection and creates an object:
typedef otb::GenericMapProjection<otb::TransformDirection::INVERSE> InverseProjectionType; InverseProjectionType::Pointer utmMapProjection = InverseProjectionType::New();
Later in the program, we can use the defined type as a template parameter for a filter:
typedef otb::OrthoRectificationFilter<ImageType, DoubleImageType, InverseProjectionType> OrthoRectifFilterType; OrthoRectifFilterType::Pointer orthoRectifPAN = OrthoRectifFilterType::New();
If we created the projection without defining the type like this:
auto utmMapProjection = otb::GenericMapProjection<otb::TransformDirection::INVERSE>::New();
how can we then instantiate the filter with a type which has not been defined? Like this:
auto orthoRectifPAN = otb::OrthoRectificationFilter<ImageType, DoubleImageType, decltype(utmMapProjection)::ObjectType>::New();
I am not sure whether this is a good solution in production code which has to be maintained long time after it was written, but it is for sure an interesting solution when prototyping processing chains.
Lambdas
Lambdas, or anonymous functions, are one of the most exciting things in C++11 when one has used them in LISP-like languages or even in Python. They make fast prototyping much easier mainly when making a heavy use of the STL algorithms because they can replace functors or plain functions.
Usually, STL algorithms can be specialised by passing them functions
as parameters. If one wants to store state, a functor or function
object (a class which defines the ()
operator) is used.
The inconvenient of functions and functors is that they are defined away from where they are used, so they are not ideal when they are only used once. Functors are worse than functions since they need much boilerplate for only one useful method.
Lambdas are the ideal solution for this, since they are defined locally. They can store state by capturing local variables either by reference or by value.
OTB iterators are not fully compatible with the STL, so one can think that lambdas would not be very useful with OTB specific code. But actually, there are many filters which can be implemented by specialising one of the N-ary functor filters. These filters are useful when one wants to implement a filtering operation which can't be obtained with the otb::BandMathFilter, as for instance when a local neighbourhood is needed.
One example of this is the change detection framework. A long time ago when I wrote this example I had to use a lot of code to achieve a simple thing like computing the mean difference around a pixel.
First of all, I needed a functor to implement the processing in the neighbourhood:
template<class TInput1, class TInput2, class TOutput> class MyChangeDetector { public: MyChangeDetector() {} ~MyChangeDetector() {} inline TOutput operator ()(const TInput1& itA, const TInput2& itB) { TOutput result = 0.0; for (unsigned long pos = 0; pos < itA.Size(); ++pos) { result += static_cast<TOutput>(itA.GetPixel(pos) - itB.GetPixel(pos)); } return static_cast<TOutput>(result / itA.Size()); } };
That is, a class definition with empty constructor and destructor. Then I needed to inherit from the binary functor which performs a neighbourhood filtering and specialise it using my functor.
template <class TInputImage1, class TInputImage2, class TOutputImage> class ITK_EXPORT MyChangeDetectorImageFilter : public otb::BinaryFunctorNeighborhoodImageFilter< TInputImage1, TInputImage2, TOutputImage, MyChangeDetector< typename itk::ConstNeighborhoodIterator<TInputImage1>, typename itk::ConstNeighborhoodIterator<TInputImage2>, typename TOutputImage::PixelType> > { public: typedef MyChangeDetectorImageFilter Self; typedef typename otb::BinaryFunctorNeighborhoodImageFilter< TInputImage1, TInputImage2, TOutputImage, MyChangeDetector< typename itk::ConstNeighborhoodIterator<TInputImage1>, typename itk::ConstNeighborhoodIterator<TInputImage2>, typename TOutputImage::PixelType> > Superclass; typedef itk::SmartPointer<Self> Pointer; typedef itk::SmartPointer<const Self> ConstPointer; itkNewMacro(Self); protected: MyChangeDetectorImageFilter() {} virtual ~MyChangeDetectorImageFilter() {} private: MyChangeDetectorImageFilter(const Self &); void operator =(const Self&); };
So again, a class definition for nothing. I could maybe just have defined a type, but the code using it would not have been as simple as this:
typedef MyChangeDetectorImageFilter<InputImageType1, InputImageType2, ChangeImageType> FilterType; FilterType::Pointer filter = FilterType::New();
Even if I had defined a type alias for the filter in order to avoid to inherit from the existing filter, the functor definition is an overkill and it is located far from where it is used.
Using lambdas, the code becomes this:
using CPT = ChangeImageType::PixelType; using CNIType1 = itk::ConstNeighborhoodIterator<InputImageType1>; using CNIType2 = itk::ConstNeighborhoodIterator<InputImageType2>;
That is, 3 lines of type aliases for a simpler notation. Then the lambda:
std::function<CPT (CNIType1, CNIType2)> cdt = [](CNIType1 itA, CNIType2 itB){ CPT result{0}; for (auto pos = 0; pos < itA.Size(); ++pos) result += static_cast<CPT>(itA.GetPixel(pos) - itB.GetPixel(pos)); return static_cast<CPT>(result / itA.Size()); };
And finally the filter instantiation:
auto filter = otb::BinaryFunctorNeighborhoodImageFilter<InputImageType1, InputImageType2, ChangeImageType, decltype(cdt)>::New();
If we had wanted to have a function with state, objects in the scope
where the lambda is defined can be captured by value or by reference
within the []
, so we are very close to what a functor may achieve
with much less code.
The main advantage here is that all the code is in the same location thanks to the possibility of creating a function anywhere in the sources. If I understand it well, lambdas are inlined, which means that the code is faster than a function call.
Conclusion
I have only started to use this kind of code, but I think that once
you start using auto
and lambdas it must be very difficult to go
back to C++98/03.
The only drawback with using C++11 is dropping compatibility with old compilers, but this is not an issue for most of the things I do. So I can start writing modern OTB code instead of poo (plain old OTB)!
Footnotes:
With GCC 4.7 this only seems to work out of a scoped block.