22.3 Pointer-to-Member Functions

Ru-Brd

To understand why a distinction is made between pointers to ordinary functions and pointers to member functions, it is useful to study the typical C++ implementation of a call to a member function. Such a call could take the form p->mf() or a close variation of this syntax. Here, p is a pointer to an object or to a subobject. It is passed in some form as a hidden parameter to mf() , where it is known as the this pointer.

The member function mf() may have been defined for the subobject pointed to by p , or it may be inherited by the subobject. For example:

 class B1 {    private:      int b1;    public:      void mf1();  };  void B1::mf1()  {      std::cout << "b1="<<b1<<std::endl;  } 

As a member function, mf1() expects to be called for an object of type B1 . Thus, this refers to to an object of type B1 .

Let's add some more code to this:

 class B2 {    private:      int b2;    public:      void mf2();  };  void B1::mf2()  {      std::cout << "b2="<<b2<<std::endl;  } 

The member mf2() similarly expects the hidden parameter this to point to a B2 subobject.

Now let's derive a class from both B1 and B2 :

 class D: public B1, public B2 {    private:      int d;  }; 

With this declaration, an object of type D can behave as an object of type B1 or an object of type B2 . For this to work, a D object contains both a B1 subobject and a B2 subobject. On nearly all 32-bit implementations we know of today, a D object will be organized as shown in Figure 22.1. That is, if the size of the int members is 4 bytes, member b1 has the address of this , member b2 has the address of this plus 4 bytes, and member d has the address of this plus 8 bytes. Note how the B1 subobject shares its origin with the origin of the D subobject, but the B2 subobject does not.

Figure 22.1. Typical organization of type D

graphics/22fig01.gif

Consider now the following elementary member function calls:

 int main()  {      D obj;      obj.mf1();      obj.mf2();  } 

The call obj.mf2() requires the address of the subobject of type B2 in obj to be passed to mf2() . Assuming the typical implementation described, this is the address of obj plus 4 bytes. It is not at all hard for a C++ compiler to generate code to perform this adjustment. Note that for the call to mf1() , this adjustment should not be done because the address of obj is also the address of the subobject of type B1 within obj .

However, with pointer-to-member functions the compiler does not know what adjustment is needed. To see this, replace the previous main() routine with the following:

 void call_memfun (D obj, void D::*pmf())  {      obj.*pmf();  }  int main()  {      D obj;      call_memfun(obj, &D::mf1);      call_memfun(obj, &D::mf2);  } 

To make the situation even more opaque to a C++ compiler, the call_memfun() and main() may be placed in different translation units.

The conclusion is that in addition to the address of the function, a pointer to a member function also needs to track the this pointer adjustment needed for a particular member function. This adjustment may change when a pointer-to-member function is casted. With our example:

 void D::*pmf_a()  = &D::mf2;  // adjustment of +4 recorded  void B2::*pmf_b() = (void (B2::*)())pmf_a;  // adjustment changed to 0  

The main purpose of this discussion is to illustrate the intrinsic difference between a pointer to a member function and a pointer to a function. However, the outline is not sufficient when it comes to virtual functions, and in practice many implementations use a three-word structure for pointers to member functions:

  1. The address of the member function, or NULL if it is a virtual function

  2. The required this adjustment

  3. A virtual function index

The details are beyond the scope of this book. If you're curious about this topic, a good introduction can be found in Stan Lippman's Inside the C++ Object Model (see [LippmanObjMod]). There you will also find that pointers to data members are typically not pointers at all, but the offsets needed to get from this to a given field (a single word of storage is sufficient for their representation).

Finally, note how "getting to a member function through a pointer-to-member function" is really a binary operation involving not only the pointer but also the object to which the pointer is applied. Hence, special pointer-to-member dereferencing operators .* and ->* were introduced into the language:

 obj.*pmf(   )  // call member function, to which  pmf  refers, for  obj  ptr->*pmf(   )  // call member function, to which  pmf  refers, for object,   // to which  ptr  refers  

In contrast, "getting to an ordinary function through a pointer" is a unary operation:

 (*ptr)() 

The dereferencing operator can be left out because it is implicit in the function call operator. The previous expression is therefore usually written as

 ptr() 

There is no such implicit form for pointers to member functions. [4]

[4] There is also no implicit decay of a member function name such as MyType::print to a pointer to that member. The ampersand is always required (for example, &MyType::print ). For ordinary functions, the implicit decay of f to &f is well known.

Ru-Brd


C++ Templates
C++ Templates: The Complete Guide
ISBN: 0201734842
EAN: 2147483647
Year: 2002
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net