From 8d53a3e48967f74dc4f34a1fcb03c5baca8d9b1a Mon Sep 17 00:00:00 2001 From: wolf Date: Wed, 23 Feb 2000 16:03:39 +0000 Subject: [PATCH] Move this report to the /reports dir. git-svn-id: https://svn.dealii.org/trunk@2485 0785d39b-7218-0410-832d-ea1e28bc413d --- .../reports/multithreading/multithreading.tex | 760 ------------------ 1 file changed, 760 deletions(-) delete mode 100644 deal.II/doc/reports/multithreading/multithreading.tex diff --git a/deal.II/doc/reports/multithreading/multithreading.tex b/deal.II/doc/reports/multithreading/multithreading.tex deleted file mode 100644 index f115abee76..0000000000 --- a/deal.II/doc/reports/multithreading/multithreading.tex +++ /dev/null @@ -1,760 +0,0 @@ -\documentclass[11pt]{article} -\usepackage{a4wide} -\begin{document} - -\begin{center} - \begin{huge} - Multithreading support in \texttt{deal.II} - \end{huge} - - \vspace*{0.5cm} - - \begin{large} - Wolfgang Bangerth\\ - University of Heidelberg\\[12pt] - February 2000 - \end{large} -\end{center} - - -\begin{abstract} - In this report, we describe the implementational techniques of - multithreading support in \texttt{deal.II}, which we use for the - parallelization of independent operations. Writing threaded programs in - \texttt{C++} is obstructed by two problems: operating system dependent - interfaces and that these interfaces are created for \texttt{C} programs - rather than for \texttt{C++}. We present our solutions to these problems and - describe first experiences using multithreading in \texttt{deal.II}. -\end{abstract} - - -\section{Background} - -Realistic finite element simulations tend to use enormous amounts of computing -time and memory. Scientists and programmers have therefore long tried to use -the combined power of several processors or computers to tackle these -problems. - -The usual approach is to use physically separated computers (e.g. clusters) or -computing units (e.g. processor nodes in a parallel computer), each of which -is equipped with its own memory, and split the problem at hand into separate -parts which are then solved on these computing units. Unfortunately, this -approach tends to pose significant problems, both for the mathematical -formulation as well as for the application programmer. - -\begin{itemize} -\item \textit{Implementational problems.} On all available parallel computers, -communicating data from one computing unit to other ones is extremely slow, -compared to access to data which is local to a computing unit. It must -therefore be restricted to the absolute minimum, if it is not to dominate -the total computing time, in which case one would lose the advantages of -parallel computing. However, avoiding communication is tedious and often -makes parallelized programs rather complex. Furtermore. debugging programs -on parallel computers is difficult. - -\item \textit{Mathematical problems.} Splitting the problem into subproblems -is most often done by subdividing the domain into subdomains and let each -computing unit solve the problem on its subdomain. However, the solution -operators of partial differential equations are usually nonlocal; for -example, a slight change in the right hand side function in a small region -changes the solution function everywhere. It is therefore obvious that the -subproblems can not be solved independently, but that some communication -will be indispensable in any case. In order to reduce the amount of -communication as much as possible, one usually uses the following iterative -strategy: solve each subproblem independently; then exchange information -with other units, such as boundary data of neighboring subdomains, and then -solve again with the new boundary data. This procedure is repeated until a -stopping criterion is reached. - -This iterative procedure poses mathematical questions: does the iteration -converge? And if so, can one guarantee an upper bound on the number of -iterations? While the first question can usually be answered with ``yes'', the -second one is critical: since non-parallelized solvers do not need this outer -subproblem iteration, parallelized programs become increasingly inefficient -with the number of these outer iterations. -\end{itemize} - -For the reasons stated above, parallelized implementations and their -mathematical background are still subject to intense research. In recent -years, however, multi-processor machines have been developed, which pose a -reasonable alternative to small parallelized computers with the advantage of -simple programming and the possibility to use the same mathematical -formulation that can also be used for single-processor machines. These -computers typically have between two and eight processors that can access the -global memory at equal cost. - -Due to this uniform memory access (UMA) architecture, communication can be -performed in the global memory and is no more costly than access to any other -memory location. Thus, there is also no more need to change the mathematical -formulation to reduce communication, and programs using this architecture look -very much like programs written for single processor machines. The purpose of -this report is to explain the techniques used in \texttt{deal.II} by which we -try to program these computers. - - - -\section{Threads} - -The basic entity for programming multi-processor machines are threads. They -represent parts of the program which are executed in parallel. On -single-processor machines, they are simulated by letting each thread run for -some time (usually a few milliseconds) before switching to the next thread. On -multi-processor machines, threads can truly be executed in parallel. In order -to let programs use more than one thread (which would be the regular -sequential program), several aspects need to be covered: -\begin{itemize} -\item How do we assign operations to different threads? Of course, operations - which depend on each other must not be executed in reverse order. This can - be achieved by only letting independent operations run on different threads, - or by using synchronisation methods. this is mostly a question of program - design and thus problem dependent, which is why both aspects will only be - briefly touched below. -\item How does the operating system and the whole programming environment - support this? -\end{itemize} -As mentioned, only the second aspect can be canonicalized, so we will treat it -first. - - -\section{Creating and managing threads} - -\subsection{Operating system dependence and ACE} - -While all relevant operating systems now support multi-threaded programs, they -all have different notions on what threads actually are on an operating system -level, how they shall be managed and created. Even on Unix systems, which are -usually well-standardized, there are at least three different and mutually -incompatible interfaces to threads: POSIX threads, Solaris threads, and Linux -threads. Some operating systems support more than one interface, but there is -no interface that is supported by all operating systems. Furthermore, other -systems like Microsoft Windows have interfaces that are incompatible to all -Unix systems. - -Writing multi-threaded programs based on the operating system interfaces is -therefore something inherently incompatible unless much effort is spent to -port it to a new system. To avoid this, we chose to use the ACE (Adaptive -Communication Environment) library which encapsulates the operating system -dependence and offers a uniform interface to the user. ACE runs on many -platforms, including most Unix systems and Windows. - -We chose ACE over other libraries, since it runs on almost all relevant -platforms, and since it is the only library which is actively developed by a -large group around Doug Schmidt at the University of Washington. Furthermore, -it also is significantly larger than only thread management, offering -interprocess communication and communication between different computers, as -well as many other services. Contrary to most other libraries, it therefore -offers both the ability to support a growing \texttt{deal.II} as well as the -prospect to support independence also with respect to future platforms. - - -\subsection{\texttt{C} interface to threads versus \texttt{C++}} - -While ACE encapsulates almost all of the synchronisation and interprocess -interface into \texttt{C++} classes, it for some reason does not do so for -tread creation. Rather it only offers the \texttt{C} interface which is that -when creating a new thread, a function is called which has the following -signature: -\begin{verbatim} - void * f (void * arg); -\end{verbatim} -Thus, only functions which take a single parameter of type \texttt{void*} and -return a \texttt{void*} may be called. Further, these functions must be global -or static member functions, as opposed to true member functions of -classes. This is not in line with the \texttt{C++} philosophy and in fact does -not fit well into \texttt{deal.II} as well: there is not a single function in -the library that has this signature. - -The task of multi-threading support in \texttt{deal.II} is therefore to -encapsulate member functions, arbitrary types and numbers of parameters, and -return types of functions into mechanisms built atop of ACE. This has been -done twice for \texttt{deal.II}, and we will explain both approaches. At -present, only the first approach is distributed with \texttt{deal.II}, since -the second is still experimental and also requires a newer compiler. The -latter approach, however, has clear advantages over the first one, which is -why we plan to switch to it in the next major version of \texttt{deal.II}. - - -\subsubsection{First approach} - -The first idea is the following: assume that we have a class -\texttt{TestClass} -\begin{verbatim} - class TestClass { - public: - void test_function (int i, double d); - }; -\end{verbatim} -and we would like to call -\texttt{test\_object}.\texttt{test\_function(1,3.1415926)} on a newly created -thread, where \texttt{test\_object} is -an object of type \texttt{TestClass}. We then need an object that encapsulates -the address of the member function, a pointer to the object for which we want -to call the function, and both parameters. This class would be suitable: -\begin{verbatim} - struct MemFunData { - typedef void (TestClass::*MemFunPtr) (int, double); - MemFunPtr mem_fun_ptr; - TestClass *test_object; - int arg1; - double arg2; - }; -\end{verbatim} - -We further need a function that satisfies the signature required by the -operating systems (or ACE, respectively) and that can call the member function -if we pass it an object of type \texttt{MemFunData}: -\begin{verbatim} - void * start_thread (void *arg_ptr) { - // first reinterpret the void* as a - // pointer to the object which - // encapsulates the arguments - // and addresses: - MemFunData *MemFunData - = reinterpret_cast(arg_ptr); - // then call the member function: - (MemFunData->test_object) - ->*(MemFunData->mem_fun_ptr) (MemFunData->arg1, - MemFunData->arg2); - // since the function does not return - // a value, we do so ourselves: - return 0; - }; -\end{verbatim} -Such functions are called \textit{trampoline functions} since they only serve -as jump-off point for other functions. - - -We can then perform the desired call using the following sequence of commands: -\begin{verbatim} - MemFunData mem_fun_data; - mem_fun_data.mem_fun_ptr = &TestClass::test_function; - mem_fun_data.test_object = &test_object; - mem_fun_data.arg1 = 1; - mem_fun_data.arg2 = 3.1415926; - - ACE_Thread_Manager::spawn (&start_thread, - (void*)&mem_fun_data); -\end{verbatim} -\texttt{ACE\_Thread\_Manager::spawn} is the function from ACE that actually calls the -operating system and tells it to call on a new thread the function which it is -given as first parameter (here: \texttt{start\_thread}) with the parameter -which is given as second parameter. \texttt{start\_thread}, when called, will -then get the address of the function which we wanted to call from its -parameter, and call it with the values we wanted as arguments. - -In practice, this would mean that we needed a structure like -\texttt{MemFunData} and a function like \texttt{start\_thread} for each class -\texttt{TestClass} and all functions \texttt{test\_function} with different -signatures. This is clearly not feasible in practice and places an -inappropriate burden on the programmer who wants to use multiple threads in -his program. Fortunately, \texttt{C++} offers an elegant way for this problem, -in the form of templates: we first define a data type which encapsulates -address and arguments for all binary functions: -\begin{verbatim} - template - struct MemFunData { - typedef void (Class::*MemFunPtr) (Arg1, Arg2); - MemFunPtr mem_fun_ptr; - Class *test_object; - Arg1 arg1; - Arg2 arg2; - }; -\end{verbatim} -Next, we need a function that can process these arguments: -\begin{verbatim} - template - void * start_thread (void *arg_ptr) { - MemFunData *MemFunData - = reinterpret_cast(arg_ptr); - (MemFunData->test_object) - ->*(MemFunData->mem_fun_ptr) (MemFunData->arg1, - MemFunData->arg2); - return 0; - }; -\end{verbatim} -Then we can start the thread as follows: -\begin{verbatim} - MemFunData mem_fun_data; - mem_fun_data.mem_fun_ptr = &TestClass::test_function; - mem_fun_data.test_object = &test_object; - mem_fun_data.arg1 = 1; - mem_fun_data.arg2 = 3.1415926; - - ACE_Thread_Manager::spawn (&start_thread, - (void*)&mem_fun_data); -\end{verbatim} -Here we first create an object which is suitable to encapsulate the parameters -of a binary function that takes an integer and a double and is a member -function of the \texttt{TestClass} class. Then we start the thread using the -correct trampoline function. It is the user's responsibility to choose the -correct trampoline function (i.e. to specify the correct template parameters) -since the compiler only sees a \texttt{void*} and cannot do any type checking. - -We can further simplify the process and remove the user responsibility by -defining the following class and function: -\begin{verbatim} - class ThreadManager : public ACE_Thread_Manager { - public: - template - static void - spawn (MemFunData &MemFunData) { - ACE_Thread_Manager::spawn (&start_thread, - (void*)&MemFunData); - }; - }; -\end{verbatim} -This way, we can call -\begin{verbatim} - ThreadManager::spawn (mem_fun_data); -\end{verbatim} -and the compiler will figure out which the right trampoline function is. - -The way described above is basically the way which is presently used in -\texttt{deal.II}. Some care has to be paid to details, however. In particular, -\texttt{C++} functions often pass references as arguments, which however are -not assignable after initialization. Therefore, the \texttt{MemFunData} class -needs to have a constructor, and arguments must be set through it. Assume, for -example, \texttt{TestClass} had a second member function -\begin{verbatim} - void f (int &i, double &d); -\end{verbatim} -Then, we would have to use \texttt{MemFunData}, -which in a form without templates would look like this: -\begin{verbatim} - struct MemFunData { - typedef void (TestClass::*MemFunPtr) (int &, double &); - MemFunPtr mem_fun_ptr; - TestClass *test_object; - int &arg1; - double &arg2; - }; -\end{verbatim} -The compiler would require us to initialize the references to the two -parameters at construction time of the \texttt{MemFunData} object, since -it is not possible in \texttt{C++} to change the object which a reference -points to after initialization. Adding a constructor to the -\texttt{MemFunData} class would then enable us to write -\begin{verbatim} - MemFunData - mem_fun_data (&test_object, - 1, - 3.1415926, - &TestClass::f); -\end{verbatim} -Non-reference arguments could then still be changed after construction. For -historical reasons, the pointer to the member function is passed as last -parameter here. - -The last point is that this interface is only usable for functions with two -parameters. Basically, the whole process has to be reiterated for any number -of parameters which we want to support. In \texttt{deal.II}, we therefore have -classes \texttt{MemFunData0} through \texttt{MemFunData10}, corresponding to -member function that do not take parameters through functions that take ten -parameters. Equivalently, we need the respective number of trampoline -functions. - -Additional thoughts must be made on virtual member functions and constant -functions. While the first is handled by the compiler (member function -pointers can also be to virtual functions, without explicitly stating so), the -latter can be achieved by writing -\texttt{MemFunData}, which would be the correct -object if \texttt{test\_function} were declared constant. - -Finally we note that it is often the case that one member function starts a -new thread by calling another member function of the same object. Thus, the -declaration most often used is the following: -\begin{verbatim} - MemFunData - mem_fun_data (this, 1, 3.1415926, &TestClass::f); -\end{verbatim} -Here, instead of an arbitrary \texttt{test\_object}, the present object is -used, which is represented by the \texttt{this} pointer. - - - -\subsubsection{Second approach} - -While the approach outlined above works satisfactorily, it has one serious -flaw: the programmer has to provide the data types of the arguments of the -member function himself. While this seems to be a simple task, in practice it -is often not, as will be explained in the sequel. - -To expose the problem, we take an example from one of the application programs -where we would like to call the function -\begin{verbatim} - template - void DoFHandler::distribute_dofs (const FiniteElement &, - const unsigned int); -\end{verbatim} -on a new thread. Correspondingly, we would need to use -\begin{verbatim} - MemFunData2, const FiniteElement &, unsigned int> - mem_fun_data (dof_handler, fe, - 0, &DoFHandler::distribute_dofs);) -\end{verbatim} -to encapsulate the parameters. However, if one forgets the \texttt{const} -specifier on the second template parameter, one receives the following error -message (using gcc 2.95.2): -\begin{verbatim} - test.cc: In method `void InterstepData<2>::wake_up(unsigned int, Interst - epData<2>::PresentAction)': - test.cc:683: instantiated from here - test.cc:186: no matching function for call to `ThreadManager::Mem_Fun_Da - ta2,FiniteElement<2> &,unsigned int>::MemFunData2 (DoFHa - ndler<2> *, const FiniteElement<2> &, int, void (DoFHandler<2>::*)(const - FiniteElement<2> &, unsigned int))' - /home/atlas1/wolf/program/newdeal/deal.II/base/include/base/thread_manag - er.h:470: candidates are: ThreadManager::MemFunData2,Fin - iteElement<2> &,unsigned int>::MemFunData2(DoFHandler<2> *, FiniteElem - ent<2> &, unsigned int, void * (DoFHandler<2>::*)(FiniteElement<2> &, un - signed int)) - /home/atlas1/wolf/program/newdeal/deal.II/base/include/base/thread_manag - er.h:480: ThreadManager::MemFunData2,Fin - iteElement<2> &,unsigned int>::MemFunData2(DoFHandler<2> *, FiniteElem - ent<2> &, unsigned int, void (DoFHandler<2>::*)(FiniteElement<2> &, unsi - gned int)) - /home/atlas1/wolf/program/newdeal/deal.II/base/include/base/thread_manag - er.h:486: ThreadManager::MemFunData2,Fin - iteElement<2> &,unsigned int>::MemFunData2(const ThreadManager::Mem_Fu - n_Data2,FiniteElement<2> &,unsigned int> &) -\end{verbatim} - -While the compiler is certainly right to complain, the message is not very -helpful. Furthermore, since interfaces to functions sometimes change, for -example by adding additional default parameters that do not show up in usual -code, programs that used to compile do no more so with messages as shown -above. - -Due to the lengthy and complex error messages, even very experienced -programmers usually need between five and ten minutes until they get an -expression like this correct. In most cases, they don't get it right in the -first attempt, so the time used for the right declaration dominates the whole -setup of starting a new thread. To circumvent this bottleneck at least in most -cases, we chose to implement a second strategy at encapsulating the parameters -of member functions. This is done in several steps: first let the compiler -find out about the right template parameters, then encapsulate the parameters, -use the objects, and finally solve some technical problems with virtual -constructors. We will treat these steps sequentially in the following. - - -\paragraph{Finding the correct template parameters.} -\texttt{C++} offers the possibility of templated functions that deduce their -template arguments themselves. This can be used as follows: assume we have a -function class -\begin{verbatim} - template - class MemFunData { ... }; -\end{verbatim} -as above, and a function -\begin{verbatim} - template - MemFunData - deduce_types (void (Class::*mem_fun_ptr)(Arg1, Arg2)) { - return MemFunData (mem_fun_ptr); - }; -\end{verbatim} -If we call this function like this: -\begin{verbatim} - deduce_types (&TestClass::test_function); -\end{verbatim} -then it can unambiguously determine the template parameters to be -\texttt{Class=TestClass}, \texttt{Arg1=int}, \texttt{Arg2=double}. - -\paragraph{Encapsulating the parameters.} -We should -not try to include the arguments right away, for example by declaring -\texttt{deduce\_types} -\begin{verbatim} - template - MemFunData - deduce_types (void (Class::*mem_fun_ptr)(Arg1, Arg2), - Arg1 arg1, - Arg2 arg2, - Class object) { - return MemFunData (mem_fun_ptr, object, arg1, arg2); - }; -\end{verbatim} -The reason is that for template functions, no parameter promotion is -performed. Thus, if we called this function as in -\begin{verbatim} - deduce_types (&TestClass::test_function, - 1, 3, - test_object); -\end{verbatim} -then the compiler would refuse this since from the function pointer it must -deduce that \texttt{Arg2=double}, but from the parameter ``3'' it must assume -that \texttt{Arg2=int}. The resulting error message would be similarly lengthy -as the one shown above. - -One could instead write \texttt{MemFunData} like this: -\begin{verbatim} - template - class MemFunData { - public: - typedef void (Class::*MemFunPtr)(Arg1, Arg2); - - MemFunData (MemFunPtr mem_fun_ptr_) { - mem_fun_ptr = mem_fun_ptr_; - }; - - void collect_args (Class *object_, - Arg1 arg1_, - Arg2 arg2_) { - object = object_; - arg1 = arg1_; - arg2 = arg2_; - }; - - MemFunPtr mem_fun_ptr; - Class *object; - Arg1 arg1; - Arg2 arg2; - }; -\end{verbatim} -One would then create an object of this type including the parameters to be -passed as follows: -\begin{verbatim} - deduce_types (&TestClass::test_function).collect_args(1, 3, - test_object); -\end{verbatim} -Here, the first function call creates an object with the right template -parameters, and the second one, calling a member function, fills in the -function arguments. - -Unfortunately, this way does not work: if one or more of the parameter types -is a reference, then the respective reference variable needs to be initialized -by the constructor, not by \texttt{collect\_args}. It needs to be known which -object the reference references at construction time, since later on only the -referenced object can be assigned, not the reference itself anymore. - -Since we feel that we are close to a solution, we introduce one more -indirection, which indeed will be the last one: -\begin{verbatim} - template - class MemFunData { - public: - typedef void (Class::*MemFunPtr)(Arg1, Arg2); - - MemFunData (MemFunPtr mem_fun_ptr_, - Class *object_, - Arg1 arg1_, - Arg2 arg2_) : - mem_fun_ptr (mem_fun_ptr_), - object (object_), - arg1 (arg1_), - arg2 (arg2_) {}; - - MemFunPtr mem_fun_ptr; - Class *object; - Arg1 arg1; - Arg2 arg2; - }; - - - template - struct Intermediate { - typedef void (Class::*MemFunPtr)(Arg1, Arg2); - - Intermediate (MemFunPtr mem_fun_ptr_) { - mem_fun_ptr = mem_fun_ptr_; - }; - - - MemFunData - collect_args (Class *object_, - Arg1 arg1_, - Arg2 arg2_) { - return MemFunData (mem_fun_ptr, object, - arg1, arg2); - }; - - MemFunPtr mem_fun_ptr; - }; - - - template - Intermediate - deduce_types (void (Class::*mem_fun_ptr)(Arg1, Arg2)) { - return Intermediate (mem_fun_ptr); - }; -\end{verbatim} - -Now we can indeed write -\begin{verbatim} - deduce_types (&TestClass::test_function).collect_args(1, 3, - test_object); -\end{verbatim} -The first call creates an object of type \texttt{Intermediate<...>} with the -right parameters, while the second call, a call to a member function of that -intermediate class, generates the final object we are interested in, including -the member function pointer and all necessary parameters. Since -\texttt{collect\_args} already has its template parameters fixed from -\texttt{deduce\_types}, it can convert between data types. - - -\paragraph{Using these objects.} Now we have an object of the correct type -automatically generated, without the need to type in any template parameters -by hand. What can we do with that? First, we can't assign it to a variable of -that type. Why? Since we would then have to write the data type of that -variable by hand, which is exactly what we wanted to avoid. However, we can do -some such thing if the variable to which we assign the result is of a type -which is a base class of \texttt{MemFunData<...>}. Unfortunately, the -parameters that \texttt{MemFunData<...>} encapsulates depend on the -template parameters, so the respective variables in which we store can only be -in the derived class and could not be copied when we assign the variable to a -base class object, since that does not have these variables. - -What can we do here? Assume we have the following structure in the library: -\begin{verbatim} - class MemFunBase {}; - - template <...> class MemFunData : public MemFunBase - { /* as above */ }; - - class MemFunEncapsulation { - public: - MemFunEncapsulation (MemFunBase *mem_fun_base) - : mem_fun_base (mem_fun_base) {}; - MemFunBase *mem_fun_base; - }; - - - template - MemFunEncapsulation - Intermediate::collect_args (Class *object_, - Arg1 arg1_, - Arg2 arg2_) { - return new MemFunData (mem_fun_ptr, object, - arg1, arg2); - }; -\end{verbatim} - -Now, the call to \texttt{deduce\_types(...).collect\_args(...)} generates an -object of type \texttt{MemFunEncapsulation}, which in turn stores a pointer to -an object of type \texttt{MemFunBase}, here to \texttt{MemFunData<...>} with -the correct template parameters. We can assigne the result to a variable the -type of which does not contain any template parameters any more, as desired: -\begin{verbatim} - MemFunEncapsulation - mem_fun_encapsulation = deduce_types (&TestClass::test_function) - .collect_args(1, 3, test_object); -\end{verbatim} - -But how can we start a thread with this object if we have lost the full -information about the data types? This can be done as follows: add am abstract -virtual function \texttt{get\_trampoline()} to \texttt{MemFunBase} which is -implemented in the derived classes -\begin{verbatim} - class MemFunBase { - public: - typedef void * (*ThreadEntryPoint) (void *); - virtual ThreadEntryPoint get_trampoline () = 0; - }; - - template <...> - class MemFunData : public MemFunBase { - public: - virtual ThreadEntryPoint get_trampoline () { - return &start_thread; - }; - - static void * start_thread (void *args) { - // do the same as in start_thread above - } - }; - - - void spawn (MemFunEncapsulation &mem_fun_encapsulation) { - ACE_Thread_Manager::spawn (mem_fun_encapsulation.mem_fun_base - ->get_trampoline()), - (void*)&mem_fun_base); - }; -\end{verbatim} -The call to \texttt{get\_trampoline} gets us the right thread starter function -which knows that the parameter it gets has the right data type to which it can -be casted. Thus, we can now write the whole sequence of function calls: -\begin{verbatim} - MemFunEncapsulation - mem_fun_encapsulation = deduce_types (&TestClass::test_function) - .collect_args(1, 3, test_object); - spawn (mem_fun_encapsulation); -\end{verbatim} -This solves our problem in that no template parameters need to be specified by -hand any more. The only source for lengthy compiler error messages is if the -parameters to \texttt{collect\_args} arg in the wrong order or can not be -casted to the parameters of the member function which we want to call. These -problems, however, are much more unlikely in our experience, and are also much -quicker sorted out. - - -\paragraph{Virtual constructors.} While the basic techniques have been fuly -developed now, there are some aspects which we still have to take care of. The -basic problem here is that the \texttt{MemFunEncapsulation} objects store a -pointer to an object that was created using the \texttt{new} operator. To -prevent a memory leak, we need to destroy this object at some time, preferably -in the destructor of \texttt{MemFunEncapsulation}: -\begin{verbatim} - MemFunEncapsulation::~MemFunEncapsulation () { - delete mem_fun_base; - }; -\end{verbatim} -However, what happens if we have copied the object before? In particular, -since this is always the case using the functions above: \texttt{collect\_args} -generates a temporary object of type \texttt{MemFunEncapsulation}, but there -could be other sources of copies as well. If we do not take special measures, -only the pointer to the object is copied around, and we end up with stale -pointers pointing to invalid locations in memory once the first object has -been destroyed. What we obviously need to do when copying objects of type -\texttt{MemFunEncapsulation} is to not copy the pointer but to copy the object -which it points to. Unfortunately, the following copy constructor is not -possible: -\begin{verbatim} - MemFunEncapsulation::MemFunEncapsulation (const MemFunEncapsulation &m) { - mem_fun_base = new MemFunBase (*m.mem_fun_base); - }; -\end{verbatim} -The reason, of course, is that we do not want to copy that part of the object -belonging to the abstract base class (besides the fact that the compiler won't -let us do so, since \texttt{MemFunEncapsulation} has abstract virtual -functions). But we can emulate something like this in the following way (the -programming idiom is called ``virtual constructors''): -\begin{verbatim} - class MemFunBase { - public: - // as above - - virtual MemFunBase * clone () const = 0; - }; - - template <...> - class MemFunData : public MemFunBase { - public: - // as above - - // copy constructor: - MemFunData (const MemFunData<...> &mem_fun_data) {...}; - - // clone the present object, i.e. - // create an exact copy: - virtual MemFunBase * clone () const { - return new MemFunData<...>(*this); - }; - }; - - - MemFunEncapsulation::MemFunEncapsulation (const MemFunEncapsulation &m) { - mem_fun_base = m.mem_fun_base->clone (); - }; -\end{verbatim} -Thus, whenever the \texttt{MemFunEncapsulation} object is copied, it creates a -copy of the object it harbours (the \texttt{MemFunData<...>} object) which it -owns. When the destructor is called, it is free to delete its copy without -affecting other objects (from which it may have been copied, or to which it -was copied). - -\end{document} - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: t -%%% End: -- 2.39.5