| [Top] | [Contents] | [Index] | [ ? ] |
For a pdf version of this manual, see http://www.nsnam.org/docs/manual.pdf.
Simulator version: ns-3.7.1
This is an ns-3 reference manual.
Primary documentation for the ns-3 project is available in
five forms:
This document is written in GNU Texinfo and is to be maintained in
revision control on the ns-3 code server. Both PDF and HTML
versions should be available on the server. Changes to
the document should be discussed on the ns-developers@isi.edu mailing list.
This software is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This manual is organized into several parts with several chapters per part. This chapter describes the overall software organization and the corresponding organization of this manual.
ns-3 is a discrete-event network simulator in which the simulation core and models are implemented in C++. ns-3 is built as a library which may be statically or dynamically linked to a C++ main program that defines the simulation topology and starts the simulator. ns-3 also exports nearly all of its API to Python, allowing Python programs to import an "ns3" module in much the same way as in C++.
Figure 1.1: Software organization of ns-3
The source code for ns-3 is mostly organized in the src/
directory and can be described by the diagram in fig:organization.
We will work our way from the bottom up; in general, modules
only have dependencies on modules beneath them in the figure.
We first describe Part 1 of the manual.
The simulation core is implemented in src/core, and the core is
used to build the simulation engine src/simulator. Packets are
fundamental objects in a network simulator and are implemented in
src/packet. These three simulation modules by themselves
are intended to comprise a generic simulation core that can be used
by different kinds of networks, not just Internet-based networks.
The above modules of ns-3 are independent of specific network and
device models, which are covered in later parts of this manual.
In addition to the above ns-3 core, we describe also in Part 1 two other modules that supplement the core C++-based API. ns-3 programs may access all of the API directly or may make use of a so-called “helper API” that provides convenient wrappers or encapsulation of low-level API calls. The fact that ns-3 programs can be written to two APIs (or a combination thereof) is a fundamental aspect of the simulator and is also covered in Part 1. We also describe how Python is supported in ns-3 as the last chapter of Part 1.
The remainder of the manual is focused on documenting the models
and supporting capabilities. Part 2 focuses on two fundamental
objects in ns-3: the Node and NetDevice. Two
special NetDevice types are designed to support network emulation
use cases, and emulation is described in Part 3.
Part 4 is devoted to Internet-related models, including the sockets
API used by Internet applications. Part 5 covers applications, and
Part 6 describes additional support for simulation, such as animators.
The project maintains a separate manual devoted to testing and validation of ns-3 code (see the ns-3 Testing and Validation manual).
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
ns-3 contains a built-in pseudo-random number generator (PRNG). It is important for serious users of the simulator to understand the functionality, configuration, and usage of this PRNG, and to decide whether it is sufficient for his or her research use.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
ns-3 random numbers are provided via instances of class RandomVariable.
SeedManager::SetSeed (uint32_t) at the beginning of the program;
to set a run number with the same seed, call
SeedManager::SetRun (uint32_t) at the beginning of the program;
See section Seeding and independent replications
Read further for more explanation about the random number facility for ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Simulations use a lot of random numbers; one study found that most network simulations spend as much as 50% of the CPU generating random numbers. Simulation users need to be concerned with the quality of the (pseudo) random numbers and the independence between different streams of random numbers.
Users need to be concerned with a few issues, such as:
We will introduce a few terms here: a RNG provides a long sequence of (pseudo) random numbers. The length of this sequence is called the cycle length or period, after which the RNG will repeat itself. This sequence can be partitioned into disjoint streams. A stream of a RNG is a contiguous subset or block of the RNG sequence. For instance, if the RNG period is of length N, and two streams are provided from this RNG, then the first stream might use the first N/2 values and the second stream might produce the second N/2 values. An important property here is that the two streams are uncorrelated. Likewise, each stream can be partitioned disjointedly to a number of uncorrelated substreams. The underlying RNG hopefully produces a pseudo-random sequence of numbers with a very long cycle length, and partitions this into streams and substreams in an efficient manner.
ns-3 uses the same underlying random number generator as does ns-2: the MRG32k3a generator from Pierre L’Ecuyer. A detailed description can be found in http://www.iro.umontreal.ca/~lecuyer/myftp/papers/streams00.pdf. The MRG32k3a generator provides 1.8x10^19 independent streams of random numbers, each of which consists of 2.3x10^15 substreams. Each substream has a period (i.e., the number of random numbers before overlap) of 7.6x10^22. The period of the entire generator is 3.1x10^57.
Class ns3::RandomVariable is the public interface to this
underlying random number generator. When users create new
RandomVariables (such as UniformVariable, ExponentialVariable,
etc.), they create an object that uses one of the distinct, independent
streams of the random number generator. Therefore, each
object of type RandomVariable has, conceptually, its own "virtual" RNG.
Furthermore, each RandomVariable can be configured to use
one of the set of substreams drawn from the main stream.
An alternate implementation would be to allow each RandomVariable to have its own (differently seeded) RNG. However, we cannot guarantee as strongly that the different sequences would be uncorrelated in such a case; hence, we prefer to use a single RNG and streams and substreams from it.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
ns-3 simulations can be configured to produce deterministic or random results. If the ns-3 simulation is configured to use a fixed, deterministic seed with the same run number, it should give the same output each time it is run.
By default, ns-3 simulations use a fixed seed and run number.
These values are stored in two ns3::GlobalValue instances:
g_rngSeed and g_rngRun.
A typical use case is to run a simulation as a sequence of independent trials, so as to compute statistics on a large number of independent runs. The user can either change the global seed and rerun the simulation, or can advance the substream state of the RNG, which is referred to as incrementing the run number.
A class ns3::SeedManager () provides an API to control
the seeding and run number behavior.
This seeding and substream state setting must be called before any
random variables are created; e.g.
SeedManager::SetSeed (3); // Changes seed from default of 1 to 3 SeedManager::SetRun (7); // Changes run number from default of 1 to 7 // Now, create random variables UniformVariable x(0,10); ExponentialVariable y(2902); ...
Which is better, setting a new seed or advancing the substream state? There is no guarantee that the streams produced by two random seeds will not overlap. The only way to guarantee that two streams do not overlap is to use the substream capability provided by the RNG implementation. Therefore, use the substream capability to produce multiple independent runs of the same simulation. In other words, the more statistically rigorous way to configure multiple independent replications is to use a fixed seed and to advance the run number. This implementation allows for a maximum of 2.3x10^15 independent replications using the substreams.
For ease of use, it is not necessary to control the seed and run number
from within the program; the user can set the
NS_GLOBAL_VALUE environment variable as follows:
NS_GLOBAL_VALUE="RngRun=3" ./waf --run program-name
Another way to control this is by passing a command-line argument; since this is an ns-3 GlobalValue instance, it is equivalently done such as follows:
./waf --command-template="%s --RngRun=3" --run program-name
or, if you are running programs directly outside of waf:
./build/optimized/scratch/program-name --RngRun=3
The above command-line variants make it easy to run lots of different runs from a shell script by just passing a different RngRun index.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
All random variables should derive from class RandomVariable.
This base class provides a few static methods for globally configuring
the behavior of the random number generator. Derived classes
provide API for drawing random variates from the particular
distribution being supported.
Each RandomVariable created in the simulation is given a generator that is a new RNGStream from the underlying PRNG. Used in this manner, the L’Ecuyer implementation allows for a maximum of 1.8x10^19 random variables. Each random variable in a single replication can produce up to 7.6x10^22 random numbers before overlapping.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Below are excerpted a few public methods of class RandomVariable
that access the next value in the substream.
|
We have already described the seeding configuration above. Different RandomVariable subclasses may have additional API.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The following types of random variables are provided, and are documented
in the ns-3 Doxygen or by reading src/core/random-variable.h. Users
can also create their own custom random variables by deriving from
class RandomVariable.
class UniformVariable
class ConstantVariable
class SequentialVariable
class ExponentialVariable
class ParetoVariable
class WeibullVariable
class NormalVariable
class EmpiricalVariable
class IntEmpiricalVariable
class DeterministicVariable
class LogNormalVariable
class TriangularVariable
class GammaVariable
class ErlangVariable
class ZipfVariable
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
RandomVariable objects have value semantics. This means that they
can be passed by value to functions. The can also be passed by
reference to const. RandomVariables do not derive from
ns3::Object and we do not use smart pointers to manage them;
they are either allocated on the stack or else users explicitly manage
any heap-allocated RandomVariables.
RandomVariable objects can also be used in ns-3 attributes, which means that values can be set for them through the ns-3 attribute system. An example is in the propagation models for WifiNetDevice:
|
Here, the ns-3 user can change the default random variable for this delay model (which is a UniformVariable ranging from 0 to 1) through the attribute system.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
There is presently no support for substituting a different underlying random number generator (e.g., the GNU Scientific Library or the Akaroa package). Patches are welcome.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
To be completed
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
When you publish simulation results, a key piece of configuration information that you should always state is how you used the the random number generator.
It is incumbent on the researcher publishing results to include enough information to allow others to reproduce his or her results. It is also incumbent on the researcher to convince oneself that the random numbers used were statistically valid, and to state in the paper why such confidence is assumed.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Let’s review what things you should do when creating a simulation.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Some new users to ns-3 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.
| 3.1 Motivation | ||
| 3.2 Background | ||
| 3.3 Using the Callback API | ||
| 3.4 Bound Callbacks | ||
3.7 Callback locations in ns-3 | ||
| 3.5 Traced Callbacks | ||
| 3.8 Implementation details |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
|
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 <typname 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;
};
N.B. The previous code is not real ns-3 code. It is simplistic example code used only to illustrate the concepts involved and to help you understand the system more. Do not expect to find this code anywhere in the ns-3 tree
Notice that there are two variables defined in the class above. The m_p variable is the object pointer and m_pmi is the variable containing the address of the function to execute.
Notice that when operator() is called, it in turn calls the method provided
with the object pointer using the C++ PMI syntax.
To use this, one could then declare some model code that takes a generic functor as a parameter
void LibraryFunction (Functor functor);
The code that will talk to the model would build a specific functor and pass it to
LibraryFunction,
MyClass myClass; SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);
When LibraryFunction is done, it executes the callback using the
operator() on the generic functor it was passed, and in this particular
case, provides the integer argument:
void
LibraryFunction (Functor functor)
{
// Ececute the library function
functor(1234);
}
Notice that LibraryFunction is completely decoupled from the specific
type of the client. The connection is made through the Functor polymorphism.
The Callback API in ns-3 implements object-oriented callbacks using
the functor mechanism. This callback API, being based on C++ templates, is
type-safe; that is, it performs static type checks to enforce proper signature
compatibility between callers and callees. It is therefore more type-safe to
use than traditional function pointers, but the syntax may look imposing at
first. This section is designed to walk you through the Callback system so
that you can be comfortable using it in ns-3.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The Callback API is fairly minimal, providing only two services:
This is best observed via walking through an example, based on
samples/main-callback.cc.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Consider a function:
static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::n="middle" align="left">[Index]
[ ? ]
3.1 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_in