mirror of
https://github.com/OPM/opm-simulators.git
synced 2025-02-25 18:55:30 -06:00
handbook: factor the design patterns chapter out into a separate file
This commit is contained in:
parent
ecc16cfd72
commit
2849154a60
480
doc/handbook/designpatterns.tex
Normal file
480
doc/handbook/designpatterns.tex
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
\chapter{Design Patterns}
|
||||||
|
\label{chap:DesignPatterns}
|
||||||
|
|
||||||
|
This chapter tries to give a high-level understanding of some of the
|
||||||
|
fundamental techniques which are used by \Dune and \Dumux and the
|
||||||
|
motivation behind these design decisions. It is assumed that the
|
||||||
|
reader already has basic experience in object oriented programming
|
||||||
|
with C++.
|
||||||
|
|
||||||
|
First, a quick motivation of C++ template programming is given. Then
|
||||||
|
follows an introduction to polymorphism and the opportunities for it
|
||||||
|
opened by the template mechanism. After that, some drawbacks
|
||||||
|
associated with template programming are discussed, in particular the
|
||||||
|
blow-up of identifier names and proliferation of template arguments
|
||||||
|
and some approaches on how to deal with these problems.
|
||||||
|
|
||||||
|
\section{C++ Template Programming}
|
||||||
|
|
||||||
|
One of the main features of modern versions of the C++ programming
|
||||||
|
language is robust support for templates. Templates are a mechanism
|
||||||
|
for code generation build directly into the compiler. For the
|
||||||
|
motivation behind templates, consider a linked list of \texttt{double}
|
||||||
|
values which could be implemented like this:
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
struct DoubleList {
|
||||||
|
DoubleList(const double &val, DoubleList *prevNode = 0)
|
||||||
|
{ value = val; if (prevNode) prevNode->next = this; };
|
||||||
|
double value;
|
||||||
|
DoubleList *next;
|
||||||
|
};
|
||||||
|
int main() {
|
||||||
|
DoubleList *head, *tail;
|
||||||
|
head = tail = new DoubleList(1.23);
|
||||||
|
tail = new DoubleList(2.34, tail);
|
||||||
|
tail = new DoubleList(3.56, tail);
|
||||||
|
};
|
||||||
|
\end{lstlisting}
|
||||||
|
But what should be done if a list of strings is also required? The
|
||||||
|
only ``clean'' way to achive this without templates would be to copy
|
||||||
|
\texttt{DoubleList}, then rename it and change the type of the
|
||||||
|
\texttt{value} attribute. It is obvious that this is a very
|
||||||
|
cumbersome, error-prone and unproductive process. For this reason,
|
||||||
|
recent standards of the C++ programming language specify the template
|
||||||
|
mechanism, which is a way let the compiler do the tedious work. Using
|
||||||
|
templates, a generic linked list can be implemented like this:
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
template <class ValueType>
|
||||||
|
struct List {
|
||||||
|
List(const ValueType &val, List *prevNode = 0)
|
||||||
|
{ value = val; if (prevNode) prevNode->next = this; };
|
||||||
|
ValueType value;
|
||||||
|
List *next;
|
||||||
|
};
|
||||||
|
int main() {
|
||||||
|
typedef List<double> DoubleList;
|
||||||
|
DoubleList *head, *tail;
|
||||||
|
head = tail = new DoubleList(1.23);
|
||||||
|
tail = new DoubleList(2.34, tail);
|
||||||
|
tail = new DoubleList(3.56, tail);
|
||||||
|
|
||||||
|
typedef List<const char*> StringList;
|
||||||
|
StringList *head2, *tail2;
|
||||||
|
head2 = tail2 = new StringList("Hello");
|
||||||
|
tail2 = new StringList(", ", tail2);
|
||||||
|
tail2 = new StringList("World!", tail2);
|
||||||
|
};
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Compared to approaches which use external tools for code generation --
|
||||||
|
which is the approach chosen for example by the
|
||||||
|
FEniCS~\cite{FENICS-HP} project -- or heavy (ab)use of the C
|
||||||
|
preprocessor -- as done for example by the UG framework~\cite{UG-HP}
|
||||||
|
-- templates have several advantages:
|
||||||
|
\begin{description}
|
||||||
|
\item[Well Programmable:] Programming errors are directly detected by
|
||||||
|
the C++ compiler. Thus, diagnostic messages from the compiler are
|
||||||
|
directly useful because the source code which gets compiled is the
|
||||||
|
same as the one written by the developer. This is not the case
|
||||||
|
for code generators and C-preprocessor macros where the actual
|
||||||
|
statements processed by the compiler are obfuscated.
|
||||||
|
\item[Easily Debugable:] Programs which use the template mechanism can be
|
||||||
|
debugged almost as easily as C++ programs which do not use
|
||||||
|
templates. This is due to the fact that the debugger always knows
|
||||||
|
the ``real'' source file and line number.
|
||||||
|
\end{description}
|
||||||
|
For these reasons \Dune and \Dumux extensively use the template
|
||||||
|
mechanism. Both projects also try to avoid duplicating functionality
|
||||||
|
provided by the Standard Template Library (STL,~\cite{STL-REF-HP})
|
||||||
|
which is part of the C++ standard and functionality provided by the
|
||||||
|
quasi-standard Boost~\cite{BOOST-HP} libraries.
|
||||||
|
|
||||||
|
\section{Polymorphism}
|
||||||
|
|
||||||
|
In object oriented programming, some methods often makes sense for all
|
||||||
|
classes in a hierarchy, but what actually needs to be \textit{done}
|
||||||
|
can differ for each concrete class. This observation motivates
|
||||||
|
\textit{polymorphism}. Fundamentally, polymorphism means all
|
||||||
|
techniques where a method call results in the processor executing code
|
||||||
|
which is specific to the type of object for which the method is
|
||||||
|
called\footnote{This is \textit{poly} of polymorphism: There are
|
||||||
|
multiple ways to achieve the same goal.}.
|
||||||
|
|
||||||
|
In C++, there are two common ways to achieve polymorphism: The
|
||||||
|
traditional dynamic polymorphism which does not require template
|
||||||
|
programming, and static polymorphism which is made possible by the
|
||||||
|
template mechanism.
|
||||||
|
|
||||||
|
\subsection*{Dynamic Polymorphism}
|
||||||
|
|
||||||
|
To utilize \textit{dynamic polymorphism} in C++, the polymorphic
|
||||||
|
methods are marked with the \texttt{virtual} keyword in the base
|
||||||
|
class. Internally, the compiler realizes dynamic polymorphism by
|
||||||
|
storing a pointer to a so-called \texttt{vtable} within each object of
|
||||||
|
polymorphic classes. The \texttt{vtable} itself stores the entry point
|
||||||
|
of each method which is declared \texttt{virtual}. If such a method is
|
||||||
|
called on an object, the compiler generates code which retrieves the
|
||||||
|
method's memory address from the object's \texttt{vtable} and then
|
||||||
|
continues execution at this address. This explains why this mechanism
|
||||||
|
is called \textbf{dynamic} polymorphism: the code which is actually
|
||||||
|
executed is dynamically determined at run time.
|
||||||
|
|
||||||
|
\begin{example}
|
||||||
|
\label{example:DynPoly}
|
||||||
|
A class called \texttt{Car} could feature the methods
|
||||||
|
\texttt{gasUsage}, which by default corrosponds to the current
|
||||||
|
$CO_2$ emission goal of the European Union but can be changed by
|
||||||
|
classes representing actual cars. Also, a method called
|
||||||
|
\texttt{fuelTankSize} makes sense for all cars, but since there is
|
||||||
|
no useful default, its \texttt{vtable} entry is set to $0$ in the
|
||||||
|
base class. This tells the compiler that it is mandatorily that this
|
||||||
|
method gets defined in derived classes. Finally, the method
|
||||||
|
\texttt{range} may calculate the expected remaining kilometers the
|
||||||
|
car can drive given a fill level of the fuel tank. Since the
|
||||||
|
\texttt{range} method can retrieve information it needs, it does not
|
||||||
|
need to be polymorphic.
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
// The base class
|
||||||
|
class Car
|
||||||
|
{public:
|
||||||
|
virtual double gasUsage()
|
||||||
|
{ return 4.5; };
|
||||||
|
virtual double fuelTankSize() = 0;
|
||||||
|
|
||||||
|
double range(double fuelTankFillLevel)
|
||||||
|
{ return 100*fuelTankFillLevel*fuelTankSize()/gasUsage(); }
|
||||||
|
};
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\noindent
|
||||||
|
Actual car models can now be derived from the base class like this:
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
// A Mercedes S-class car
|
||||||
|
class S : public Car
|
||||||
|
{public:
|
||||||
|
virtual double gasUsage() { return 9.0; };
|
||||||
|
virtual double fuelTankSize() { return 65.0; };
|
||||||
|
};
|
||||||
|
|
||||||
|
// A VW Lupo
|
||||||
|
class Lupo : public Car
|
||||||
|
{public:
|
||||||
|
virtual double gasUsage() { return 2.99; };
|
||||||
|
virtual double fuelTankSize() { return 30.0; };
|
||||||
|
};
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\noindent
|
||||||
|
Calling the \texttt{range} method yields the correct result for any
|
||||||
|
kind of car:
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
void printMaxRange(Car &car)
|
||||||
|
{ std::cout << "Maximum Range: " << car.range(1.00) << "\n"; }
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Lupo lupo;
|
||||||
|
S s;
|
||||||
|
std::cout << "VW Lupo:";
|
||||||
|
std::cout << "Median range: " << lupo.range(0.50) << "\n";
|
||||||
|
printMaxRange(lupo);
|
||||||
|
std::cout << "Mercedes S-Class:";
|
||||||
|
std::cout << "Median range: " << s.range(0.50) << "\n";
|
||||||
|
printMaxRange(s);
|
||||||
|
}
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
For both types of cars, \texttt{Lupo} and \texttt{S} the
|
||||||
|
\texttt{printMaxRange} function works as expected, yielding
|
||||||
|
$1003.3\;\mathrm{km}$ for the Lupo and $722.2\;\mathrm{km}$ for the
|
||||||
|
S-Class.
|
||||||
|
\end{example}
|
||||||
|
|
||||||
|
\begin{exc}
|
||||||
|
What happens if \dots
|
||||||
|
\begin{itemize}
|
||||||
|
\item \dots the \texttt{gasUsage} method is removed from the \texttt{Lupo} class?
|
||||||
|
\item \dots the \texttt{virtual} qualifier is removed in front of the
|
||||||
|
\texttt{gasUsage} method in the base class?
|
||||||
|
\item \dots the \texttt{fuelTankSize} method is removed from the \texttt{Lupo} class?
|
||||||
|
\item \dots the \texttt{range} method in the \texttt{S} class is
|
||||||
|
overwritten?
|
||||||
|
\end{itemize}
|
||||||
|
\end{exc}
|
||||||
|
|
||||||
|
\subsection*{Static Polymorphism}
|
||||||
|
|
||||||
|
Dynamic polymorphism has a few disadvantages. The most relevant in the
|
||||||
|
context of \Dumux is that the compiler can not see ``inside'' the
|
||||||
|
called methods and thus cannot optimize properly. For example, modern
|
||||||
|
C++ compilers 'inline' short methods, i.e. they copy the method's body
|
||||||
|
to where it is called. First, inlining allows to save a few
|
||||||
|
instructions by avoiding to jump into and out of the method. Second,
|
||||||
|
and more importantly, inlining also allows further optimizations which
|
||||||
|
depend on specific properties of the function arguments (e.g. constant
|
||||||
|
value elimination) or the contents of the function body (e.g. loop
|
||||||
|
unrolling). Unfortunately, inlining and other cross-method
|
||||||
|
optimizations are made next to impossible by dynamic
|
||||||
|
polymorphism. This is because these optimizations are done by the
|
||||||
|
compiler (i.e. at compile time) whilst the code which actually gets
|
||||||
|
executed is only determined at run time for \texttt{virtual}
|
||||||
|
methods. To overcome this issue, template programming can be used to
|
||||||
|
achive polymorphism at compile time. This works by supplying the type
|
||||||
|
of the derived class as an additional template parameter to the base
|
||||||
|
class. Whenever the base class needs to call back the derived class,
|
||||||
|
the \texttt{this} pointer of the base class is reinterpreted as a
|
||||||
|
being a pointer to an object of the derived class and the method is
|
||||||
|
then called on the reinterpreted pointer. This scheme gives the C++
|
||||||
|
compiler complete transparency of the code executed and thus opens
|
||||||
|
much better optimization oportunities. Since this mechanism completely
|
||||||
|
happens at compile time, it is called ``static polymorphism'' because
|
||||||
|
the called method cannot be dynamically changed at runtime.
|
||||||
|
\begin{example}
|
||||||
|
Using static polymorphism, the base class of example \ref{example:DynPoly}
|
||||||
|
can be implemented like:
|
||||||
|
\begin{lstlisting}[name=staticcars,basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
// The base class. The 'Imp' template parameter is the
|
||||||
|
// type of the implementation, i.e. the derived class
|
||||||
|
template <class Imp>
|
||||||
|
class Car
|
||||||
|
{public:
|
||||||
|
double gasUsage()
|
||||||
|
{ return 4.5; };
|
||||||
|
double fuelTankSize()
|
||||||
|
{ throw "The derived class needs to implement the fuelTankSize() method"; };
|
||||||
|
|
||||||
|
double range(double fuelTankFillLevel)
|
||||||
|
{ return 100*fuelTankFillLevel*asImp_().fuelTankSize()/asImp_().gasUsage(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// reinterpret 'this' as a pointer to an object of type 'Imp'
|
||||||
|
Imp &asImp_() { return *static_cast<Imp*>(this); }
|
||||||
|
};
|
||||||
|
\end{lstlisting}
|
||||||
|
(Notice the \texttt{asImp\_()} calls in the \texttt{range} method.) The
|
||||||
|
derived classes may now be defined like this:
|
||||||
|
\begin{lstlisting}[name=staticcars,basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
// A Mercedes S-class car
|
||||||
|
class S : public Car<S>
|
||||||
|
{public:
|
||||||
|
double gasUsage() { return 9.0; };
|
||||||
|
double fuelTankSize() { return 65.0; };
|
||||||
|
};
|
||||||
|
|
||||||
|
// A VW Lupo
|
||||||
|
class Lupo : public Car<Lupo>
|
||||||
|
{public:
|
||||||
|
double gasUsage() { return 2.99; };
|
||||||
|
double fuelTankSize() { return 30.0; };
|
||||||
|
};
|
||||||
|
\end{lstlisting}
|
||||||
|
\end{example}
|
||||||
|
|
||||||
|
\noindent
|
||||||
|
Analogous to example \ref{example:DynPoly}, the two kinds of cars can
|
||||||
|
be used generically within (template) functions:
|
||||||
|
\begin{lstlisting}[name=staticcars,basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
template <class CarType>
|
||||||
|
void printMaxRange(CarType &car)
|
||||||
|
{ std::cout << "Maximum Range: " << car.range(1.00) << "\n"; }
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Lupo lupo;
|
||||||
|
S s;
|
||||||
|
std::cout << "VW Lupo:";
|
||||||
|
std::cout << "Median range: " << lupo.range(0.50) << "\n";
|
||||||
|
printMaxRange(lupo);
|
||||||
|
std::cout << "Mercedes S-Class:";
|
||||||
|
std::cout << "Median range: " << s.range(0.50) << "\n";
|
||||||
|
printMaxRange(s);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
%\textbf{TODO: Exercise}
|
||||||
|
|
||||||
|
\section{Common Template Programming Related Problems}
|
||||||
|
|
||||||
|
Although C++ template programming opens a few intriguing
|
||||||
|
possibilities, it also has a few disadvantages. In this section, a few
|
||||||
|
of them are outlined and some hints on how they can be dealt with are
|
||||||
|
provided.
|
||||||
|
|
||||||
|
\subsection*{Identifier-Name Blow-Up}
|
||||||
|
|
||||||
|
One particular problem with advanced use of C++ templates is that the
|
||||||
|
canonical identifier names for types and methods quickly become really
|
||||||
|
long and unintelligible. For example, a typical error message
|
||||||
|
generated using GCC 4.5 and \Dune-PDELab looks like
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize, numbersep=5pt]
|
||||||
|
test_pdelab.cc:171:9: error: no matching function for call to ‘Dune::\
|
||||||
|
PDELab::GridOperatorSpace<Dune::PDELab::PowerGridFunctionSpace<Dune::\
|
||||||
|
PDELab::GridFunctionSpace<Dune::GridView<Dune::DefaultLeafGridViewTraits\
|
||||||
|
<const Dune::UGGrid<3>, (Dune::PartitionIteratorType)4u> >, Dune::\
|
||||||
|
PDELab::Q1LocalFiniteElementMap<double, double, 3>, Dune::PDELab::\
|
||||||
|
NoConstraints, Dumux::PDELab::BoxISTLVectorBackend<Dumux::Properties::\
|
||||||
|
TTag::LensProblem> >, 2, Dune::PDELab::GridFunctionSpaceBlockwiseMapper>\
|
||||||
|
, Dune::PDELab::PowerGridFunctionSpace<Dune::PDELab::GridFunctionSpace<\
|
||||||
|
Dune::GridView<Dune::DefaultLeafGridViewTraits<const Dune::UGGrid<3>, \
|
||||||
|
(Dune::PartitionIteratorType)4u> >, Dune::PDELab::Q1LocalFiniteElementMap\
|
||||||
|
<double, double, 3>, Dune::PDELab::NoConstraints, Dumux::PDELab::\
|
||||||
|
BoxISTLVectorBackend<Dumux::Properties::TTag::LensProblem> >, 2, Dune::\
|
||||||
|
PDELab::GridFunctionSpaceBlockwiseMapper>, Dumux::PDELab::BoxLocalOperator\
|
||||||
|
<Dumux::Properties::TTag::LensProblem>, Dune::PDELab::\
|
||||||
|
ConstraintsTransformation<long unsigned int, double>, Dune::PDELab::\
|
||||||
|
ConstraintsTransformation<long unsigned int, double>, Dune::PDELab::\
|
||||||
|
ISTLBCRSMatrixBackend<2, 2>, true>::GridOperatorSpace()’
|
||||||
|
\end{lstlisting}
|
||||||
|
This seriously complicates diagnostics. Although there is no full
|
||||||
|
solution for this problem yet, an effective way of dealing with such
|
||||||
|
kinds of error messages is to ignore the type information and to just
|
||||||
|
look at the location given at the beginning of the line. If nested
|
||||||
|
templates are used, the lines printed by the compiler above the actual
|
||||||
|
error message specify how exactly the code was instantiated (the lines
|
||||||
|
starting with ``\texttt{instantiated from}''). In this case it is
|
||||||
|
advisable to look at the innermost source code location of ``recently
|
||||||
|
added'' source code.
|
||||||
|
|
||||||
|
\subsection*{Proliferation of Template Parameters}
|
||||||
|
|
||||||
|
Templates often need a large number of template parameters. For
|
||||||
|
example, the error message above was produced by the following
|
||||||
|
snipplet:
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
enum {numEq = 2};
|
||||||
|
enum {dim = 3};
|
||||||
|
typedef Dune::UGGrid<dim> Grid;
|
||||||
|
typedef Grid::LeafGridView GridView;
|
||||||
|
typedef Dune::PDELab::Q1LocalFiniteElementMap<double,double,dim> FEM;
|
||||||
|
typedef TTAG(LensProblem) TypeTag;
|
||||||
|
typedef Dune::PDELab::NoConstraints Constraints;
|
||||||
|
typedef Dune::PDELab::GridFunctionSpace<
|
||||||
|
GridView, FEM, Constraints, Dumux::PDELab::BoxISTLVectorBackend<TypeTag>
|
||||||
|
>
|
||||||
|
doubleGridFunctionSpace;
|
||||||
|
typedef Dune::PDELab::PowerGridFunctionSpace<
|
||||||
|
doubleGridFunctionSpace,
|
||||||
|
numEq,
|
||||||
|
Dune::PDELab::GridFunctionSpaceBlockwiseMapper
|
||||||
|
>
|
||||||
|
GridFunctionSpace;
|
||||||
|
typedef typename GridFunctionSpace::ConstraintsContainer<double>::Type
|
||||||
|
ConstraintsTrafo;
|
||||||
|
typedef Dumux::PDELab::BoxLocalOperator<TypeTag> LocalOperator;
|
||||||
|
typedef Dune::PDELab::GridOperatorSpace<
|
||||||
|
GridFunctionSpace,
|
||||||
|
GridFunctionSpace,
|
||||||
|
LocalOperator,
|
||||||
|
ConstraintsTrafo,
|
||||||
|
ConstraintsTrafo,
|
||||||
|
Dune::PDELab::ISTLBCRSMatrixBackend<numEq, numEq>,
|
||||||
|
true
|
||||||
|
>
|
||||||
|
GOS;
|
||||||
|
GOS gos; // instantiate grid operator space
|
||||||
|
}
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Although the code above is not really intuitivly readable, this does
|
||||||
|
not pose a severe problem as long as the type (in this case the grid
|
||||||
|
operator space) needs to be specified exactly once in the whole
|
||||||
|
program. If, on the other hand, it needs to be consistend over
|
||||||
|
multiple locations in the source code, measures have to be taken in
|
||||||
|
order to keep the code maintainable.
|
||||||
|
|
||||||
|
\section{Traits Classes}
|
||||||
|
|
||||||
|
A classic approach to reduce the number of template parameters is to
|
||||||
|
gather the all arguments in a special class, a so-called traits
|
||||||
|
class. Instead of writing
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
template <class A, class B, class C, class D>
|
||||||
|
class MyClass {};
|
||||||
|
\end{lstlisting}
|
||||||
|
one can use
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
template <class Traits>
|
||||||
|
class MyClass {};
|
||||||
|
\end{lstlisting}
|
||||||
|
where the \texttt{Traits} class contains public type definitions for
|
||||||
|
\texttt{A}, \texttt{B}, \texttt{C} and \texttt{D}, e.g.
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
struct MyTraits
|
||||||
|
{
|
||||||
|
typedef float A;
|
||||||
|
typedef double B;
|
||||||
|
typedef short C;
|
||||||
|
typedef int D;
|
||||||
|
};
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\noindent
|
||||||
|
As there is no a free lunch, the traits approach comes with a few
|
||||||
|
disadvantages of its own:
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Hierarchies of traits classes are problematic. This is due to
|
||||||
|
the fact that each level of the hierarchy must be self-contained. As
|
||||||
|
a result it is impossible to define parameters in the base class
|
||||||
|
which depend on parameters which only later get specified by a
|
||||||
|
derived traits class.
|
||||||
|
\item Traits quickly lead to circular dependencies. In practice
|
||||||
|
this means that traits classes can not extract any information from
|
||||||
|
templates which get the traits class as an argument -- even if the
|
||||||
|
extracted information does not require the traits class.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
\noindent
|
||||||
|
To see the point of the first issue, consider the following:
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
struct MyBaseTraits {
|
||||||
|
typedef int Scalar;
|
||||||
|
typedef std::vector<Scalar> Vector;
|
||||||
|
typedef std::list<Scalar> List;
|
||||||
|
typedef std::array<Scalar> Array;
|
||||||
|
typedef std::set<Scalar> Set;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MyDoubleTraits : public MyBaseTraits {
|
||||||
|
typedef double Scalar;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
MyDoubleTraits::Vector v{1.41421, 1.73205, 2};
|
||||||
|
for (int i = 0; i < v.size(); ++i)
|
||||||
|
std::cout << v[i]*v[i] << std::endl;
|
||||||
|
}
|
||||||
|
\end{lstlisting}
|
||||||
|
Contrary to what is intended, \texttt{v} is a vector of integers. This
|
||||||
|
problem cannot also be solved using static polymorphism, since it
|
||||||
|
would lead to a cyclic dependency between \texttt{MyBaseTraits} and
|
||||||
|
\texttt{MyDoubleTraits}.
|
||||||
|
|
||||||
|
The second issue is illuminated by the following example, where one
|
||||||
|
would expect the \texttt{MyTraits:: VectorType} to be \texttt{std::vector<double>}:
|
||||||
|
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
||||||
|
template <class Traits>
|
||||||
|
class MyClass {
|
||||||
|
public: typedef double ScalarType;
|
||||||
|
private: typedef typename Traits::VectorType VectorType;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MyTraits {
|
||||||
|
typedef MyClass<MyTraits>::ScalarType ScalarType;
|
||||||
|
typedef std::vector<ScalarType> VectorType
|
||||||
|
};
|
||||||
|
\end{lstlisting}
|
||||||
|
Although this example seems to be quite pathetic, in practice it is
|
||||||
|
often useful to specify parameters in such a way.
|
||||||
|
|
||||||
|
% TODO: section about separation of functions, parameters and
|
||||||
|
% independent variables. (e.g. the material laws: BrooksCorey
|
||||||
|
% (function), BrooksCoreyParams (function parameters), wetting
|
||||||
|
% saturation/fluid state (independent variables)
|
||||||
|
|
||||||
|
%%% Local Variables:
|
||||||
|
%%% mode: latex
|
||||||
|
%%% TeX-master: "dumux-handbook"
|
||||||
|
%%% End:
|
@ -109,6 +109,7 @@ Universit\"at Stuttgart, Paffenwaldring 61, D-70569 Stuttgart, Germany}\\
|
|||||||
|
|
||||||
\input{intro}
|
\input{intro}
|
||||||
\input{getting-started}
|
\input{getting-started}
|
||||||
|
\input{designpatterns}
|
||||||
\input{propertysystem}
|
\input{propertysystem}
|
||||||
\input{tutorial}
|
\input{tutorial}
|
||||||
\input{structure}
|
\input{structure}
|
||||||
|
@ -1,479 +1,3 @@
|
|||||||
\chapter{Design Patterns}
|
|
||||||
\label{chap:DesignPatterns}
|
|
||||||
|
|
||||||
This chapter tries to give a high-level understanding of some of the
|
|
||||||
fundamental techniques which are used by \Dune and \Dumux and the
|
|
||||||
motivation behind these design decisions. It is assumed that the
|
|
||||||
reader already has basic experience in object oriented programming
|
|
||||||
with C++.
|
|
||||||
|
|
||||||
First, a quick motivation of C++ template programming is given. Then
|
|
||||||
follows an introduction to polymorphism and the opportunities for it
|
|
||||||
opened by the template mechanism. After that, some drawbacks
|
|
||||||
associated with template programming are discussed, in particular the
|
|
||||||
blow-up of identifier names and proliferation of template arguments
|
|
||||||
and some approaches on how to deal with these problems.
|
|
||||||
|
|
||||||
\section{C++ Template Programming}
|
|
||||||
|
|
||||||
One of the main features of modern versions of the C++ programming
|
|
||||||
language is robust support for templates. Templates are a mechanism
|
|
||||||
for code generation build directly into the compiler. For the
|
|
||||||
motivation behind templates, consider a linked list of \texttt{double}
|
|
||||||
values which could be implemented like this:
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
struct DoubleList {
|
|
||||||
DoubleList(const double &val, DoubleList *prevNode = 0)
|
|
||||||
{ value = val; if (prevNode) prevNode->next = this; };
|
|
||||||
double value;
|
|
||||||
DoubleList *next;
|
|
||||||
};
|
|
||||||
int main() {
|
|
||||||
DoubleList *head, *tail;
|
|
||||||
head = tail = new DoubleList(1.23);
|
|
||||||
tail = new DoubleList(2.34, tail);
|
|
||||||
tail = new DoubleList(3.56, tail);
|
|
||||||
};
|
|
||||||
\end{lstlisting}
|
|
||||||
But what should be done if a list of strings is also required? The
|
|
||||||
only ``clean'' way to achive this without templates would be to copy
|
|
||||||
\texttt{DoubleList}, then rename it and change the type of the
|
|
||||||
\texttt{value} attribute. It is obvious that this is a very
|
|
||||||
cumbersome, error-prone and unproductive process. For this reason,
|
|
||||||
recent standards of the C++ programming language specify the template
|
|
||||||
mechanism, which is a way let the compiler do the tedious work. Using
|
|
||||||
templates, a generic linked list can be implemented like this:
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
template <class ValueType>
|
|
||||||
struct List {
|
|
||||||
List(const ValueType &val, List *prevNode = 0)
|
|
||||||
{ value = val; if (prevNode) prevNode->next = this; };
|
|
||||||
ValueType value;
|
|
||||||
List *next;
|
|
||||||
};
|
|
||||||
int main() {
|
|
||||||
typedef List<double> DoubleList;
|
|
||||||
DoubleList *head, *tail;
|
|
||||||
head = tail = new DoubleList(1.23);
|
|
||||||
tail = new DoubleList(2.34, tail);
|
|
||||||
tail = new DoubleList(3.56, tail);
|
|
||||||
|
|
||||||
typedef List<const char*> StringList;
|
|
||||||
StringList *head2, *tail2;
|
|
||||||
head2 = tail2 = new StringList("Hello");
|
|
||||||
tail2 = new StringList(", ", tail2);
|
|
||||||
tail2 = new StringList("World!", tail2);
|
|
||||||
};
|
|
||||||
\end{lstlisting}
|
|
||||||
|
|
||||||
Compared to approaches which use external tools for code generation --
|
|
||||||
which is the approach chosen for example by the
|
|
||||||
FEniCS~\cite{FENICS-HP} project -- or heavy (ab)use of the C
|
|
||||||
preprocessor -- as done for example by the UG framework~\cite{UG-HP}
|
|
||||||
-- templates have several advantages:
|
|
||||||
\begin{description}
|
|
||||||
\item[Well Programmable:] Programming errors are directly detected by
|
|
||||||
the C++ compiler. Thus, diagnostic messages from the compiler are
|
|
||||||
directly useful because the source code which gets compiled is the
|
|
||||||
same as the one written by the developer. This is not the case
|
|
||||||
for code generators and C-preprocessor macros where the actual
|
|
||||||
statements processed by the compiler are obfuscated.
|
|
||||||
\item[Easily Debugable:] Programs which use the template mechanism can be
|
|
||||||
debugged almost as easily as C++ programs which do not use
|
|
||||||
templates. This is due to the fact that the debugger always knows
|
|
||||||
the ``real'' source file and line number.
|
|
||||||
\end{description}
|
|
||||||
For these reasons \Dune and \Dumux extensively use the template
|
|
||||||
mechanism. Both projects also try to avoid duplicating functionality
|
|
||||||
provided by the Standard Template Library (STL,~\cite{STL-REF-HP})
|
|
||||||
which is part of the C++ standard and functionality provided by the
|
|
||||||
quasi-standard Boost~\cite{BOOST-HP} libraries.
|
|
||||||
|
|
||||||
\section{Polymorphism}
|
|
||||||
|
|
||||||
In object oriented programming, some methods often makes sense for all
|
|
||||||
classes in a hierarchy, but what actually needs to be \textit{done}
|
|
||||||
can differ for each concrete class. This observation motivates
|
|
||||||
\textit{polymorphism}. Fundamentally, polymorphism means all
|
|
||||||
techniques where a method call results in the processor executing code
|
|
||||||
which is specific to the type of object for which the method is
|
|
||||||
called\footnote{This is \textit{poly} of polymorphism: There are
|
|
||||||
multiple ways to achieve the same goal.}.
|
|
||||||
|
|
||||||
In C++, there are two common ways to achieve polymorphism: The
|
|
||||||
traditional dynamic polymorphism which does not require template
|
|
||||||
programming, and static polymorphism which is made possible by the
|
|
||||||
template mechanism.
|
|
||||||
|
|
||||||
\subsection*{Dynamic Polymorphism}
|
|
||||||
|
|
||||||
To utilize \textit{dynamic polymorphism} in C++, the polymorphic
|
|
||||||
methods are marked with the \texttt{virtual} keyword in the base
|
|
||||||
class. Internally, the compiler realizes dynamic polymorphism by
|
|
||||||
storing a pointer to a so-called \texttt{vtable} within each object of
|
|
||||||
polymorphic classes. The \texttt{vtable} itself stores the entry point
|
|
||||||
of each method which is declared \texttt{virtual}. If such a method is
|
|
||||||
called on an object, the compiler generates code which retrieves the
|
|
||||||
method's memory address from the object's \texttt{vtable} and then
|
|
||||||
continues execution at this address. This explains why this mechanism
|
|
||||||
is called \textbf{dynamic} polymorphism: the code which is actually
|
|
||||||
executed is dynamically determined at run time.
|
|
||||||
|
|
||||||
\begin{example}
|
|
||||||
\label{example:DynPoly}
|
|
||||||
A class called \texttt{Car} could feature the methods
|
|
||||||
\texttt{gasUsage}, which by default corrosponds to the current
|
|
||||||
$CO_2$ emission goal of the European Union but can be changed by
|
|
||||||
classes representing actual cars. Also, a method called
|
|
||||||
\texttt{fuelTankSize} makes sense for all cars, but since there is
|
|
||||||
no useful default, its \texttt{vtable} entry is set to $0$ in the
|
|
||||||
base class. This tells the compiler that it is mandatorily that this
|
|
||||||
method gets defined in derived classes. Finally, the method
|
|
||||||
\texttt{range} may calculate the expected remaining kilometers the
|
|
||||||
car can drive given a fill level of the fuel tank. Since the
|
|
||||||
\texttt{range} method can retrieve information it needs, it does not
|
|
||||||
need to be polymorphic.
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
// The base class
|
|
||||||
class Car
|
|
||||||
{public:
|
|
||||||
virtual double gasUsage()
|
|
||||||
{ return 4.5; };
|
|
||||||
virtual double fuelTankSize() = 0;
|
|
||||||
|
|
||||||
double range(double fuelTankFillLevel)
|
|
||||||
{ return 100*fuelTankFillLevel*fuelTankSize()/gasUsage(); }
|
|
||||||
};
|
|
||||||
\end{lstlisting}
|
|
||||||
|
|
||||||
\noindent
|
|
||||||
Actual car models can now be derived from the base class like this:
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
// A Mercedes S-class car
|
|
||||||
class S : public Car
|
|
||||||
{public:
|
|
||||||
virtual double gasUsage() { return 9.0; };
|
|
||||||
virtual double fuelTankSize() { return 65.0; };
|
|
||||||
};
|
|
||||||
|
|
||||||
// A VW Lupo
|
|
||||||
class Lupo : public Car
|
|
||||||
{public:
|
|
||||||
virtual double gasUsage() { return 2.99; };
|
|
||||||
virtual double fuelTankSize() { return 30.0; };
|
|
||||||
};
|
|
||||||
\end{lstlisting}
|
|
||||||
|
|
||||||
\noindent
|
|
||||||
Calling the \texttt{range} method yields the correct result for any
|
|
||||||
kind of car:
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
void printMaxRange(Car &car)
|
|
||||||
{ std::cout << "Maximum Range: " << car.range(1.00) << "\n"; }
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
Lupo lupo;
|
|
||||||
S s;
|
|
||||||
std::cout << "VW Lupo:";
|
|
||||||
std::cout << "Median range: " << lupo.range(0.50) << "\n";
|
|
||||||
printMaxRange(lupo);
|
|
||||||
std::cout << "Mercedes S-Class:";
|
|
||||||
std::cout << "Median range: " << s.range(0.50) << "\n";
|
|
||||||
printMaxRange(s);
|
|
||||||
}
|
|
||||||
\end{lstlisting}
|
|
||||||
|
|
||||||
For both types of cars, \texttt{Lupo} and \texttt{S} the
|
|
||||||
\texttt{printMaxRange} function works as expected, yielding
|
|
||||||
$1003.3\;\mathrm{km}$ for the Lupo and $722.2\;\mathrm{km}$ for the
|
|
||||||
S-Class.
|
|
||||||
\end{example}
|
|
||||||
|
|
||||||
\begin{exc}
|
|
||||||
What happens if \dots
|
|
||||||
\begin{itemize}
|
|
||||||
\item \dots the \texttt{gasUsage} method is removed from the \texttt{Lupo} class?
|
|
||||||
\item \dots the \texttt{virtual} qualifier is removed in front of the
|
|
||||||
\texttt{gasUsage} method in the base class?
|
|
||||||
\item \dots the \texttt{fuelTankSize} method is removed from the \texttt{Lupo} class?
|
|
||||||
\item \dots the \texttt{range} method in the \texttt{S} class is
|
|
||||||
overwritten?
|
|
||||||
\end{itemize}
|
|
||||||
\end{exc}
|
|
||||||
|
|
||||||
\subsection*{Static Polymorphism}
|
|
||||||
|
|
||||||
Dynamic polymorphism has a few disadvantages. The most relevant in the
|
|
||||||
context of \Dumux is that the compiler can not see ``inside'' the
|
|
||||||
called methods and thus cannot optimize properly. For example, modern
|
|
||||||
C++ compilers 'inline' short methods, i.e. they copy the method's body
|
|
||||||
to where it is called. First, inlining allows to save a few
|
|
||||||
instructions by avoiding to jump into and out of the method. Second,
|
|
||||||
and more importantly, inlining also allows further optimizations which
|
|
||||||
depend on specific properties of the function arguments (e.g. constant
|
|
||||||
value elimination) or the contents of the function body (e.g. loop
|
|
||||||
unrolling). Unfortunately, inlining and other cross-method
|
|
||||||
optimizations are made next to impossible by dynamic
|
|
||||||
polymorphism. This is because these optimizations are done by the
|
|
||||||
compiler (i.e. at compile time) whilst the code which actually gets
|
|
||||||
executed is only determined at run time for \texttt{virtual}
|
|
||||||
methods. To overcome this issue, template programming can be used to
|
|
||||||
achive polymorphism at compile time. This works by supplying the type
|
|
||||||
of the derived class as an additional template parameter to the base
|
|
||||||
class. Whenever the base class needs to call back the derived class,
|
|
||||||
the \texttt{this} pointer of the base class is reinterpreted as a
|
|
||||||
being a pointer to an object of the derived class and the method is
|
|
||||||
then called on the reinterpreted pointer. This scheme gives the C++
|
|
||||||
compiler complete transparency of the code executed and thus opens
|
|
||||||
much better optimization oportunities. Since this mechanism completely
|
|
||||||
happens at compile time, it is called ``static polymorphism'' because
|
|
||||||
the called method cannot be dynamically changed at runtime.
|
|
||||||
\begin{example}
|
|
||||||
Using static polymorphism, the base class of example \ref{example:DynPoly}
|
|
||||||
can be implemented like:
|
|
||||||
\begin{lstlisting}[name=staticcars,basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
// The base class. The 'Imp' template parameter is the
|
|
||||||
// type of the implementation, i.e. the derived class
|
|
||||||
template <class Imp>
|
|
||||||
class Car
|
|
||||||
{public:
|
|
||||||
double gasUsage()
|
|
||||||
{ return 4.5; };
|
|
||||||
double fuelTankSize()
|
|
||||||
{ throw "The derived class needs to implement the fuelTankSize() method"; };
|
|
||||||
|
|
||||||
double range(double fuelTankFillLevel)
|
|
||||||
{ return 100*fuelTankFillLevel*asImp_().fuelTankSize()/asImp_().gasUsage(); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// reinterpret 'this' as a pointer to an object of type 'Imp'
|
|
||||||
Imp &asImp_() { return *static_cast<Imp*>(this); }
|
|
||||||
};
|
|
||||||
\end{lstlisting}
|
|
||||||
(Notice the \texttt{asImp\_()} calls in the \texttt{range} method.) The
|
|
||||||
derived classes may now be defined like this:
|
|
||||||
\begin{lstlisting}[name=staticcars,basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
// A Mercedes S-class car
|
|
||||||
class S : public Car<S>
|
|
||||||
{public:
|
|
||||||
double gasUsage() { return 9.0; };
|
|
||||||
double fuelTankSize() { return 65.0; };
|
|
||||||
};
|
|
||||||
|
|
||||||
// A VW Lupo
|
|
||||||
class Lupo : public Car<Lupo>
|
|
||||||
{public:
|
|
||||||
double gasUsage() { return 2.99; };
|
|
||||||
double fuelTankSize() { return 30.0; };
|
|
||||||
};
|
|
||||||
\end{lstlisting}
|
|
||||||
\end{example}
|
|
||||||
|
|
||||||
\noindent
|
|
||||||
Analogous to example \ref{example:DynPoly}, the two kinds of cars can
|
|
||||||
be used generically within (template) functions:
|
|
||||||
\begin{lstlisting}[name=staticcars,basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
template <class CarType>
|
|
||||||
void printMaxRange(CarType &car)
|
|
||||||
{ std::cout << "Maximum Range: " << car.range(1.00) << "\n"; }
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
Lupo lupo;
|
|
||||||
S s;
|
|
||||||
std::cout << "VW Lupo:";
|
|
||||||
std::cout << "Median range: " << lupo.range(0.50) << "\n";
|
|
||||||
printMaxRange(lupo);
|
|
||||||
std::cout << "Mercedes S-Class:";
|
|
||||||
std::cout << "Median range: " << s.range(0.50) << "\n";
|
|
||||||
printMaxRange(s);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
\end{lstlisting}
|
|
||||||
|
|
||||||
%\textbf{TODO: Exercise}
|
|
||||||
|
|
||||||
\section{Common Template Programming Related Problems}
|
|
||||||
|
|
||||||
Although C++ template programming opens a few intriguing
|
|
||||||
possibilities, it also has a few disadvantages. In this section, a few
|
|
||||||
of them are outlined and some hints on how they can be dealt with are
|
|
||||||
provided.
|
|
||||||
|
|
||||||
\subsection*{Identifier-Name Blow-Up}
|
|
||||||
|
|
||||||
One particular problem with advanced use of C++ templates is that the
|
|
||||||
canonical identifier names for types and methods quickly become really
|
|
||||||
long and unintelligible. For example, a typical error message
|
|
||||||
generated using GCC 4.5 and \Dune-PDELab looks like
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize, numbersep=5pt]
|
|
||||||
test_pdelab.cc:171:9: error: no matching function for call to ‘Dune::\
|
|
||||||
PDELab::GridOperatorSpace<Dune::PDELab::PowerGridFunctionSpace<Dune::\
|
|
||||||
PDELab::GridFunctionSpace<Dune::GridView<Dune::DefaultLeafGridViewTraits\
|
|
||||||
<const Dune::UGGrid<3>, (Dune::PartitionIteratorType)4u> >, Dune::\
|
|
||||||
PDELab::Q1LocalFiniteElementMap<double, double, 3>, Dune::PDELab::\
|
|
||||||
NoConstraints, Dumux::PDELab::BoxISTLVectorBackend<Dumux::Properties::\
|
|
||||||
TTag::LensProblem> >, 2, Dune::PDELab::GridFunctionSpaceBlockwiseMapper>\
|
|
||||||
, Dune::PDELab::PowerGridFunctionSpace<Dune::PDELab::GridFunctionSpace<\
|
|
||||||
Dune::GridView<Dune::DefaultLeafGridViewTraits<const Dune::UGGrid<3>, \
|
|
||||||
(Dune::PartitionIteratorType)4u> >, Dune::PDELab::Q1LocalFiniteElementMap\
|
|
||||||
<double, double, 3>, Dune::PDELab::NoConstraints, Dumux::PDELab::\
|
|
||||||
BoxISTLVectorBackend<Dumux::Properties::TTag::LensProblem> >, 2, Dune::\
|
|
||||||
PDELab::GridFunctionSpaceBlockwiseMapper>, Dumux::PDELab::BoxLocalOperator\
|
|
||||||
<Dumux::Properties::TTag::LensProblem>, Dune::PDELab::\
|
|
||||||
ConstraintsTransformation<long unsigned int, double>, Dune::PDELab::\
|
|
||||||
ConstraintsTransformation<long unsigned int, double>, Dune::PDELab::\
|
|
||||||
ISTLBCRSMatrixBackend<2, 2>, true>::GridOperatorSpace()’
|
|
||||||
\end{lstlisting}
|
|
||||||
This seriously complicates diagnostics. Although there is no full
|
|
||||||
solution for this problem yet, an effective way of dealing with such
|
|
||||||
kinds of error messages is to ignore the type information and to just
|
|
||||||
look at the location given at the beginning of the line. If nested
|
|
||||||
templates are used, the lines printed by the compiler above the actual
|
|
||||||
error message specify how exactly the code was instantiated (the lines
|
|
||||||
starting with ``\texttt{instantiated from}''). In this case it is
|
|
||||||
advisable to look at the innermost source code location of ``recently
|
|
||||||
added'' source code.
|
|
||||||
|
|
||||||
\subsection*{Proliferation of Template Parameters}
|
|
||||||
|
|
||||||
Templates often need a large number of template parameters. For
|
|
||||||
example, the error message above was produced by the following
|
|
||||||
snipplet:
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
enum {numEq = 2};
|
|
||||||
enum {dim = 3};
|
|
||||||
typedef Dune::UGGrid<dim> Grid;
|
|
||||||
typedef Grid::LeafGridView GridView;
|
|
||||||
typedef Dune::PDELab::Q1LocalFiniteElementMap<double,double,dim> FEM;
|
|
||||||
typedef TTAG(LensProblem) TypeTag;
|
|
||||||
typedef Dune::PDELab::NoConstraints Constraints;
|
|
||||||
typedef Dune::PDELab::GridFunctionSpace<
|
|
||||||
GridView, FEM, Constraints, Dumux::PDELab::BoxISTLVectorBackend<TypeTag>
|
|
||||||
>
|
|
||||||
doubleGridFunctionSpace;
|
|
||||||
typedef Dune::PDELab::PowerGridFunctionSpace<
|
|
||||||
doubleGridFunctionSpace,
|
|
||||||
numEq,
|
|
||||||
Dune::PDELab::GridFunctionSpaceBlockwiseMapper
|
|
||||||
>
|
|
||||||
GridFunctionSpace;
|
|
||||||
typedef typename GridFunctionSpace::ConstraintsContainer<double>::Type
|
|
||||||
ConstraintsTrafo;
|
|
||||||
typedef Dumux::PDELab::BoxLocalOperator<TypeTag> LocalOperator;
|
|
||||||
typedef Dune::PDELab::GridOperatorSpace<
|
|
||||||
GridFunctionSpace,
|
|
||||||
GridFunctionSpace,
|
|
||||||
LocalOperator,
|
|
||||||
ConstraintsTrafo,
|
|
||||||
ConstraintsTrafo,
|
|
||||||
Dune::PDELab::ISTLBCRSMatrixBackend<numEq, numEq>,
|
|
||||||
true
|
|
||||||
>
|
|
||||||
GOS;
|
|
||||||
GOS gos; // instantiate grid operator space
|
|
||||||
}
|
|
||||||
\end{lstlisting}
|
|
||||||
|
|
||||||
Although the code above is not really intuitivly readable, this does
|
|
||||||
not pose a severe problem as long as the type (in this case the grid
|
|
||||||
operator space) needs to be specified exactly once in the whole
|
|
||||||
program. If, on the other hand, it needs to be consistend over
|
|
||||||
multiple locations in the source code, measures have to be taken in
|
|
||||||
order to keep the code maintainable.
|
|
||||||
|
|
||||||
\section{Traits Classes}
|
|
||||||
|
|
||||||
A classic approach to reduce the number of template parameters is to
|
|
||||||
gather the all arguments in a special class, a so-called traits
|
|
||||||
class. Instead of writing
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
template <class A, class B, class C, class D>
|
|
||||||
class MyClass {};
|
|
||||||
\end{lstlisting}
|
|
||||||
one can use
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
template <class Traits>
|
|
||||||
class MyClass {};
|
|
||||||
\end{lstlisting}
|
|
||||||
where the \texttt{Traits} class contains public type definitions for
|
|
||||||
\texttt{A}, \texttt{B}, \texttt{C} and \texttt{D}, e.g.
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
struct MyTraits
|
|
||||||
{
|
|
||||||
typedef float A;
|
|
||||||
typedef double B;
|
|
||||||
typedef short C;
|
|
||||||
typedef int D;
|
|
||||||
};
|
|
||||||
\end{lstlisting}
|
|
||||||
|
|
||||||
\noindent
|
|
||||||
As there is no a free lunch, the traits approach comes with a few
|
|
||||||
disadvantages of its own:
|
|
||||||
\begin{enumerate}
|
|
||||||
\item Hierarchies of traits classes are problematic. This is due to
|
|
||||||
the fact that each level of the hierarchy must be self-contained. As
|
|
||||||
a result it is impossible to define parameters in the base class
|
|
||||||
which depend on parameters which only later get specified by a
|
|
||||||
derived traits class.
|
|
||||||
\item Traits quickly lead to circular dependencies. In practice
|
|
||||||
this means that traits classes can not extract any information from
|
|
||||||
templates which get the traits class as an argument -- even if the
|
|
||||||
extracted information does not require the traits class.
|
|
||||||
\end{enumerate}
|
|
||||||
|
|
||||||
\noindent
|
|
||||||
To see the point of the first issue, consider the following:
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
struct MyBaseTraits {
|
|
||||||
typedef int Scalar;
|
|
||||||
typedef std::vector<Scalar> Vector;
|
|
||||||
typedef std::list<Scalar> List;
|
|
||||||
typedef std::array<Scalar> Array;
|
|
||||||
typedef std::set<Scalar> Set;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MyDoubleTraits : public MyBaseTraits {
|
|
||||||
typedef double Scalar;
|
|
||||||
};
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
MyDoubleTraits::Vector v{1.41421, 1.73205, 2};
|
|
||||||
for (int i = 0; i < v.size(); ++i)
|
|
||||||
std::cout << v[i]*v[i] << std::endl;
|
|
||||||
}
|
|
||||||
\end{lstlisting}
|
|
||||||
Contrary to what is intended, \texttt{v} is a vector of integers. This
|
|
||||||
problem cannot also be solved using static polymorphism, since it
|
|
||||||
would lead to a cyclic dependency between \texttt{MyBaseTraits} and
|
|
||||||
\texttt{MyDoubleTraits}.
|
|
||||||
|
|
||||||
The second issue is illuminated by the following example, where one
|
|
||||||
would expect the \texttt{MyTraits:: VectorType} to be \texttt{std::vector<double>}:
|
|
||||||
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize,numbers=left,numberstyle=\tiny, numbersep=5pt]
|
|
||||||
template <class Traits>
|
|
||||||
class MyClass {
|
|
||||||
public: typedef double ScalarType;
|
|
||||||
private: typedef typename Traits::VectorType VectorType;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MyTraits {
|
|
||||||
typedef MyClass<MyTraits>::ScalarType ScalarType;
|
|
||||||
typedef std::vector<ScalarType> VectorType
|
|
||||||
};
|
|
||||||
\end{lstlisting}
|
|
||||||
Although this example seems to be quite pathetic, in practice it is
|
|
||||||
often useful to specify parameters in such a way.
|
|
||||||
|
|
||||||
% TODO: section about separation of functions, parameters and
|
|
||||||
% independent variables. (e.g. the material laws: BrooksCorey
|
|
||||||
% (function), BrooksCoreyParams (function parameters), wetting
|
|
||||||
% saturation/fluid state (independent variables)
|
|
||||||
|
|
||||||
\chapter{The \Dumux Property System}
|
\chapter{The \Dumux Property System}
|
||||||
\label{sec:propertysystem}
|
\label{sec:propertysystem}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user