Files
unison/doc/manual/source/callbacks.rst
2010-12-30 23:39:27 +01:00

236 lines
8.7 KiB
ReStructuredText

.. include:: replace.txt
Callbacks
---------
Some new users to |ns3| are unfamiliar with an extensively used programming
idiom used throughout the code: the *ns-3 callback*. This chapter provides some
motivation on the callback, guidance on how to use it, and details on its
implementation.
Callbacks Motivation
********************
Consider that you have two simulation models A and B, and you wish to have them
pass information between them during the simulation. One way that you can do
that is that you can make A and B each explicitly knowledgeable about the other,
so that they can invoke methods on each other::
class A {
public:
void ReceiveInput ( // parameters );
...
}
(in another source file:)
class B {
public:
void DoSomething (void);
...
private:
A* a_instance; // pointer to an A
}
void
B::DoSomething()
{
// Tell a_instance that something happened
a_instance->ReceiveInput ( // parameters);
...
}
This certainly works, but it has the drawback that it introduces a dependency on
A and B to know about the other at compile time (this makes it harder to have
independent compilation units in the simulator) and is not generalized; if in a
later usage scenario, B needs to talk to a completely different C object, the
source code for B needs to be changed to add a ``c_instance`` and so forth. It
is easy to see that this is a brute force mechanism of communication that can
lead to programming cruft in the models.
This is not to say that objects should not know about one another if there is a
hard dependency between them, but that often the model can be made more flexible
if its interactions are less constrained at compile time.
This is not an abstract problem for network simulation research, but rather it
has been a source of problems in previous simulators, when researchers want to
extend or modify the system to do different things (as they are apt to do in
research). Consider, for example, a user who wants to add an IPsec security
protocol sublayer between TCP and IP::
------------ -----------
| TCP | | TCP |
------------ -----------
| becomes -> |
----------- -----------
| IP | | IPsec |
----------- -----------
|
-----------
| IP |
-----------
If the simulator has made assumptions, and hard coded into the code, that IP
always talks to a transport protocol above, the user may be forced to hack the
system to get the desired interconnections. This is clearly not an optimal way
to design a generic simulator.
Callbacks Background
********************
.. note:: Readers familiar with programming callbacks may skip this tutorial
section.
The basic mechanism that allows one to address the problem above is known as a
*callback*. The ultimate goal is to allow one piece of code to call a function
(or method in C++) without any specific inter-module dependency.
This ultimately means you need some kind of indirection -- you treat the address
of the called function as a variable. This variable is called a
pointer-to-function variable. The relationship between function and
pointer-to-function pointer is really no different that that of object and
pointer-to-object.
In C the canonical example of a pointer-to-function is a
pointer-to-function-returning-integer (PFI). For a PFI taking one int parameter,
this could be declared like,::
int (*pfi)(int arg) = 0;
What you get from this is a variable named simply ``pfi`` that is initialized to
the value 0. If you want to initialize this pointer to something meaningful, you
have to have a function with a matching signature. In this case::
int MyFunction (int arg) {}
If you have this target, you can initialize the variable to point to your
function like::
pfi = MyFunction;
You can then call MyFunction indirectly using the more suggestive form of the
call::
int result = (*pfi) (1234);
This is suggestive since it looks like you are dereferencing the function
pointer just like you would dereference any pointer. Typically, however, people
take advantage of the fact that the compiler knows what is going on and will
just use a shorter form::
int result = pfi (1234);
Notice that the function pointer obeys value semantics, so you can pass it
around like any other value. Typically, when you use an asynchronous interface
you will pass some entity like this to a function which will perform an action
and *call back* to let you know it completed. It calls back by following the
indirection and executing the provided function.
In C++ you have the added complexity of objects. The analogy with the PFI above
means you have a pointer to a member function returning an int (PMI) instead of
the pointer to function returning an int (PFI).
The declaration of the variable providing the indirection looks only slightly
different::
int (MyClass::*pmi) (int arg) = 0;
This declares a variable named ``pmi`` just as the previous example declared a
variable named ``pfi``. Since the will be to call a method of an instance of a
particular class, one must declare that method in a class::
class MyClass {
public:
int MyMethod (int arg);
};
Given this class declaration, one would then initialize that variable like
this::
pmi = &MyClass::MyMethod;
This assigns the address of the code implementing the method to the variable,
completing the indirection. In order to call a method, the code needs a ``this``
pointer. This, in turn, means there must be an object of MyClass to refer to. A
simplistic example of this is just calling a method indirectly (think virtual
function)::
int (MyClass::*pmi) (int arg) = 0; // Declare a PMI
pmi = &MyClass::MyMethod; // Point at the implementation code
MyClass myClass; // Need an instance of the class
(myClass.*pmi) (1234); // Call the method with an object ptr
Just like in the C example, you can use this in an asynchronous call to another
module which will *call back* using a method and an object pointer. The
straightforward extension one might consider is to pass a pointer to the object
and the PMI variable. The module would just do::
(*objectPtr.*pmi) (1234);
to execute the callback on the desired object.
One might ask at this time, *what's the point*? The called module will have to
understand the concrete type of the calling object in order to properly make the
callback. Why not just accept this, pass the correctly typed object pointer and
do ``object->Method(1234)`` in the code instead of the callback? This is
precisely the problem described above. What is needed is a way to decouple the
calling function from the called class completely. This requirement led to the
development of the *Functor*.
A functor is the outgrowth of something invented in the 1960s called a closure.
It is basically just a packaged-up function call, possibly with some state.
A functor has two parts, a specific part and a generic part, related through
inheritance. The calling code (the code that executes the callback) will execute
a generic overloaded ``operator ()`` of a generic functor to cause the callback
to be called. The called code (the code that wants to be called back) will have
to provide a specialized implementation of the ``operator ()`` that performs the
class-specific work that caused the close-coupling problem above.
With the specific functor and its overloaded ``operator ()`` created, the called
code then gives the specialized code to the module that will execute the
callback (the calling code).
The calling code will take a generic functor as a parameter, so an implicit cast
is done in the function call to convert the specific functor to a generic
functor. This means that the calling module just needs to understand the
generic functor type. It is decoupled from the calling code completely.
The information one needs to make a specific functor is the object pointer and
the pointer-to-method address.
The essence of what needs to happen is that the system declares a generic part
of the functor::
template <typename T>
class Functor
{
public:
virtual void operator() (T arg) = 0;
};
The caller defines a specific part of the functor that really is just there to
implement the specific ``operator()`` method::
template <typename T, typename ARG>
class SpecificFunctor : public Functor
{
public:
SpecificFunctor(T* p, int (T::*_pmi)(ARG arg))
{
m_p = p;
m_pmi = pmi;
}
virtual int operator() (ARG arg)
{
(*m_p.*m_pmi)(arg);
}
private:
void (T::*m_pmi)(ARG arg);
T* m_p;
};