[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2 Background

Readers familiar with programming callbacks may skip this tutorial section.

The basic mechanism that allows one to address the problem above is known as a callback. The ultimate goal is to allow one piece of code to call a function (or method in C++) without any specific inter-module dependency.

This ultimately means you need some kind of indirection – you treat the address of the called function as a variable. This variable is called a pointer-to-function variable. The relationship between function and pointer-to-function pointer is really no different that that of object and pointer-to-object.

In C the canonical example of a pointer-to-function is a pointer-to-function-returning-integer (PFI). For a PFI taking one int parameter, this could be declared like,

  int (*pfi)(int arg) = 0;

What you get from this is a variable named simply “pfi” that is initialized to the value 0. If you want to initialize this pointer to something meaningful, you have to have a function with a matching signature. In this case,

  int MyFunction (int arg) {}

If you have this target, you can initialize the variable to point to your function like,

  pfi = MyFunction;

You can then call MyFunction indirectly using the more suggestive form of the call,

  int result = (*pfi) (1234);

This is suggestive since it looks like you are dereferencing the function pointer just like you would dereference any pointer. Typically, however, people take advantage of the fact that the compiler knows what is going on and will just use a shorter form,

  int result = pfi (1234);

Notice that the function pointer obeys value semantics, so you can pass it around like any other value. Typically, when you use an asynchronous interface you will pass some entity like this to a function which will perform an action and “call back” to let you know it completed. It calls back by following the indirection and executing the provided function.

In C++ you have the added complexity of objects. The analogy with the PFI above means you have a pointer to a member function returning an int (PMI) instead of the pointer to function returning an int (PFI).

The declaration of the variable providing the indirection looks only slightly different,

  int (MyClass::*pmi) (int arg) = 0;

This declares a variable named “pmi” just as the previous example declared a variable named “pfi.” Since the will be to call a method of an instance of a particular class, one must declare that method in a class.

  class MyClass {
  public:
    int MyMethod (int arg);
  };

Given this class declaration, one would then initialize that variable like this,

  pmi = &MyClass::MyMethod;

This assigns the address of the code implementing the method to the variable, completing the indirection. In order to call a method, the code needs a “this” pointer. This, in turn, means there must be an object of MyClass to refer to. A simplistic example of this is just calling a method indirectly (think virtual function).

  int (MyClass::*pmi) (int arg) = 0;  // Declare a PMI
  pmi = &MyClass::MyMethod;           // Point at the implementation code

  MyClass myClass;                    // Need an instance of the class
  (myClass.*pmi) (1234);              // Call the method with an object ptr

Just like in the C example, you can use this in an asynchronous call to another module which will “call back” using a method and an object pointer. The straightforward extension one might consider is to pass a pointer to the object and the PMI variable. The module would just do,

  (*objectPtr.*pmi) (1234);

to execute the callback on the desired object.

One might ask at this time, “what’s the point”? The called module will have to understand the concrete type of the calling object in order to properly make the callback. Why not just accept this, pass the correctly typed object pointer and do object->Method(1234) in the code instead of the callback? This is precisely the problem described above. What is needed is a way to decouple the calling function from the called class completely. This requirement led to the development of the Functor.

A functor is the outgrowth of something invented in the 1960s called a closure. It is basically just a packaged-up function call, possibly with some state.

A functor has two parts, a specific part and a generic part, related through inheritance. The calling code (the code that executes the callback) will execute a generic overloaded operator () of a generic functor to cause the callback to be called. The called code (the code that wants to be called back) will have to provide a specialized implementation of the operator () that performs the class-specific work that caused the close-coupling problem above.

With the specific functor and its overloaded operator () created, the called code then gives the specialized code to the module that will execute the callback (the calling code).

The calling code will take a generic functor as a parameter, so an implicit cast is done in the function call to convert the specific functor to a generic functor. This means that the calling module just needs to understand the generic functor type. It is decoupled from the calling code completely.

The information one needs to make a specific functor is the object pointer and the pointer-to-method address.

The essence of what needs to happen is that the system declares a generic part of the functor,

  template <typename T>
  class Functor
  {
  public:
      virtual void operator() (T arg) = 0;
   };

The caller defines a specific part of the functor that really is just there to implement the specific operator() method,

  template <typename T, typename ARG>
  class SpecificFunctor : public Functor
   {
   public:
      SpecificFunctor(T* p, int (T::*_pmi)(ARG arg))
      {
        m_p = p;
        m_pmi = pmi;
      }

      virtual int operator() (ARG arg)
      {
        (*m_p.*m_pmi)(arg);
      }
  private:
      void (T::*m_pmi)(ARG arg);
      T* m_p;
   };

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)
  {
    // Execute 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 ] [ >> ]

This document was generated on August 20, 2010 using texi2html 1.82.