We're always interested in getting feedback. E-mail us if you like this guide, if you think that important material is omitted, if you encounter errors in the code examples or in the documentation, if you find any typos, or generally just if you feel like e-mailing. Send your email to Frank Brokken.Please state the document version you're referring to, as found in the title (in this document: 4.4.2).
Pointers in classes have been discussed in detail in chapter 6.1. As we have seen, when pointer data-members occur in classes, such classes deserve some special treatment.
By now it is well known how to treat pointer data members: constructors are used to initialize pointers, destructors are needed to free the memory pointed to by the pointer data members.
Furthermore, in classes having pointer data members copy constructors and overloaded assignment operators are normally needed as well.
However, in some situations we do not need a pointer to an object, but rather a pointer to members of an object. The realization of pointers to members of an object is the subject of this part of the C++ annotations.
class String { public: ... char const *get() const; private: ... char const *(*sp)() const; };Within this class, it is not possible to define a
char const *(*sp)() const
pointing to the get()
member function of the String
class.
One of the reasons why this doesn't work is that the variable sp
has a global scope, while the member function get()
is defined
within the String
class. The fact that the variable sp
is part of
the String
class is of no relevance. According to sp
's definition,
it points to a function outside of the class.
Consequently, in order to define a
pointer to a member (either data or function, but usually a function) of a
class, the scope of the pointer must be within the class' scope.
Doing so, a pointer to a member of the class String
can be defined as
char const *(String::*sp)() const;So, due to the
String::
prefix, sp
is defined to be active
only in the context of the class String
. In this context, it is
defined as a pointer to a const function, not expecting arguments, and
returning a pointer to const chars.
Initializing or assigning an address to such a pointer does nothing but indicating which member the pointer will point to. However, member functions (except for the static member functions) can only be used when associated with an object of the member function's class. The same holds true for pointers to data members.
While it is allowed to initialize such a pointer outside of the class, it is not possible to access such a function without an associated object.
In the following example these characteristics
are illustrated. First, a pointer is initialized to point to
the function String::get()
. In this case no String
object is
required.
Next, a String
object is defined, and the string that is stored within the
object is retrieved through the pointer, and not directly by the function
String::get()
. Note that the pointer is a
variable existing outside of the class' context.
This presents no problem, as the actual
object to be used is identified by the statement in which object and
pointervariable are combined. Consider the following piece of code:
void fun() { char const *(String::*sp)() const; sp = String::get; // assign the address // of String's get() // function String // define a String object s("Hello world"); cout << (s.*sp)() // show the string << endl; String *ps; // pointer to a String object ps = &s; // initialize ps to point at s cout << (ps->*sp)() // show the string again << endl; }Note in this example the statement
(s.*sp)()
. The .*
construction
indicates that sp
is a pointer to a member function. Since the
pointer variable sp
points to the String::get()
function,
this function is now called, producing the string ``Hello world
''.
Furthermore, note the parentheses around (s.*sp)
. These parentheses are
required. If they were omitted, then the default interpretation (now
parenthesized for further emphasis) would be s.* (sp())
.
This latter construction means
sp()
, which should return a pointer to a
member. E.g., sp()
has the prototype
char const * (String::*)() sp();
sp()
is a
function returning a pointer to a member function of the class
String
, while such a member function must return a pointer to
const char
s.
sp
is concerned.
When a pointer to a member function is associated with an object, the pointer
to member selector operator .*
is used. When a pointer to an object
is used (instead of the object itself) the ``pointer to member through a
pointer to a class object'' operator ->*
operator is required. The
use of this operator is also illustrated in the above example.
When these static members are public, they can be accessed in a `stand-alone' fashion.
Assume that the String
class also has a public static member function
int n_strings()
, returning the number of string objects created so
far. Then, without using any String
object the function
String::n_strings()
may be called:
void fun() { cout << String::n_strings() << endl; }Since pointers to members are always associated with an object, the use of a pointer to a member function would normally produce an error. However, static members are actually global variables or functions, bound to their class.
Public static members can be treated as globally accessible functions and data. Private static members, on the other hand, can be accessed only from within the context of their class: they can only be accessed from inside the member functions of their class.
Since static members have no particular link with objects of their class, but look a lot like global functions, a pointer variable that is not part of the class of the member function must be used.
Consequently, a variable int (*pfi)()
can be used to point to the
static member function int String::n_strings()
, even though int (*pfi)()
has nothing in common with the class String
. This is illustrated in
the next example:
void fun() { int (*pfi)(); pfi = String::n_strings; // address of the static member function cout << pfi() << endl; // print the value produced by // String::n_strings() }
Person
, which are, in turn, stored
in a class Person_dbase
. Partial interfaces of these classes could be
designed as follows:
class Date; class Person() { public: ... char const *get_name() const; Date const &birthdate() const; private: ... }; class Person_dbase { public: enum Listtype { list_by_name, list_by_birthday, }; void list(Listtype type); private: Person *pp; // pointer to the info unsigned n; // number of persons stored. };The organization of
Person
and Person_dbase
is pictured in
figure 10: Within a Person_dbase
object the Person
objects
are stored. They can be reached via the pointer variable Person *pp
.
We would like to develop the function Person_dbase::list()
in such a
way that it lists the contents of the database sorted according to a selected
field of a Person
object.
So, when list()
is called to list the
database sorted by names, the database of Person
objects is first
sorted by names, and is then listed.
Alternatively, when list()
is called to list the
database sorted by birthdates, the database of Person
objects
is first sorted by birthdates, and is then listed.
In this situation, the function qsort()
is most likely called to do
the actual sorting of the Person
objects (
In the current implementation pp
points to an array of Person
objects. In this implementation, the function qsort()
will have to
copy the actual Person
objects again and again, which may be rather
inefficient when the Person
objects become large. Under an alternative
implementation, in which the Person
objects are reached through
pointers, the efficiency of the qsort()
function will be improved. In that
case, the datamember pp
will have to be declared as
Person **pp
.).
This function requires a pointer to a compare function, comparing
two elements of the array to be sorted. The prototype of this compare function
is
int (*)(void const *, void const *)
Person
objects, the prototype of the compare()
function should be
int (*)(Person const *, Person const *)
qsort()
, or within the
compare()
functions themselves. We will use the typecast when calling
qsort()
, using the following typedef to reduce the verbosity of the
typecasts
(a pointer to an integer function requiring two void pointers):
typedef int (*pif2vp)(void const *, void const *)
Next, the function list()
could be developed according to the following
setup:
void Person_dbase::list(Listtype type) { switch (type) { case list_by_name: qsort(pp, n, sizeof(Person), (pif2vp)cmpname); break; case list_by_birthday: qsort(pp, n, sizeof(Person), (pif2vp)cmpdate); break; } // list the sorted Person-database }There are several reasons why this setup is not particularly desirable:
list()
which will be hard to maintain and will
look inaccessible due to its length.
list()
will have to be expanded, by offering an extra
case
label for every new way to list the data.
list()
will be repeated
within the function, showing only some small differences.
Much of the complexity of list()
function could be reduced by
defining pointers to the compare-functions, storing these pointers in an
array. Since this array will be common to all Person_dbase
objects,
it should be defined as a static array, containing the pointers to the
compare-functions.
Before actually constructing this array, note that this approach requires the
definition of as many compare functions as there are elements in the
Listtype
enum. So, to list the information sorted by name a function
cmpname()
is used, comparing the names stored in two
Person
objects, while a function cmpcity()
, is used to compare
cities. Somehow this seems to be redundant as well: we would like to use
one function to compare strings, whatever their meanings. Comparable
considerations hold true for other fields of information.
The compare functions, however, receive pointers to Person
objects. Therefore, the data-members of the Person
objects to which
these pointers point can be accessed using the access-member functions
of the Person
class. So, the compare functions can access these
data-members as well, using the pointers to the Person
objects.
Now note that the access member functions that are used within a particular compare function can be hard-coded, by plainly mentioning the accessors to be used, and they can be selected indirectly, by using pointers to the accessors to be used.
This latter solution allows us to merge compare functions that
use the same implementations, but use different accessors: By setting a pointer
to the appropriate accessor function just before the compare function is
called, one single compare function can be used to compare many different
kinds of data stored inside Person
objects.
The compare functions themselves are used within the context of the
Person_dbase
class, where they are passed to the qsort()
function. The
qsort()
function, however, is a global function. Consequently, the compare
functions can't be ordinary member functions of the class Person_dbase
,
but they must be static members of that class, so they can be passed to the
qsort()
function.
Summarizing what we've got so far, we see that the problem has been broken down as follows:
switch
construction in the list()
function
should be replaced by a call to a function using a pointer to
a function.
list()
when it's
called.
compare()
functions may be further
abstracted by combining those comparing the same types.
compare()
functions are combined, the access
member function of the Person
objects to be used will also
be found via an array containing pointers to the access member
functions of Person
objects.
compare()
functions are part of the
Person_dbase
class, but it must also be possible to
give their addresses as arguments to qsort()
. Hence, these
functions must be defined as static functions of the class
Person_dbase
.
From this analysis the essential characteristics of the proposed implementation emerge.
For every type of listing, as produced by the function list()
, the
following is required:
Person
class to be
used.
compare()
function to be used. The compare()
functions
will be static functions of the class Person_dbase
, so that
they can be passed over to qsort()
This information does not depend on a particular Person_dbase
object,
but is common to all of these objects. Hence it will be stored compile-time
in a static Person_dbase
kind of array.
How will the compare()
functions know which element of this array to
use? The requested index is passed to the list()
member function
as a Listtype
value. The list()
function can then save this
information in a static Person_dbase::Listtype
variable for the
compare()
functions to use.
We've analyzed enough. Let's build it this way.
Date
is assumed, containing overloaded operators
like <
and >
to compare dates.
To start with, we present the interface of the class
Person
, omitting all the standard stuff like overloaded assignment
operator, (copy) constructors, etc.:
#include <stdlib.h> // for qsort() class Date; class Person() { public: unsigned length() const; unsigned weight() const; char const *name() const; char const *city() const; Date const &birthdate() const; private: // all necessary data members };
Person_dbase
.
Within this class a struct CmpPerson
is defined, containing
two fields:
As the compare functions
are static functions of the class Person_dbase
,
pointers to these functions are indiscernible from pointers to
functions at the global (::
) level. The compare functions
return int
s (for qsort()
), and expect two
pointers to Person const
objects. The field
persons
expects the two pointers to
Person const
objects. The field voids
is the alternate interpretation, to be used with
qsort()
, instead of the typecast (pif2vp)
.
pf
(pointer to access function) of
the nested union Person_accessor
.
The types of as many different
access functions of the Person
class as are used in
the class are declared in this union.
Access functions returning int
s, char const *
s and
Date &
s will be needed. Consequently, the
Person_accessor
union contains these (three) types.
From this CmpPerson
struct a static array
cmpPerson[]
is constructed. It is a
static Person_dbase
array, making it possible for the
compare functions to inspect its elements (
The number of elements of the cmpPerson[]
array is not specified
in the interface: that number is determined compile-time by the
compiler, when the static variable cmpPerson[]
is initialized.).
Also note the static Listtype selector
. This variable
will be used later in the compare functions to find the actual
Person
access function to be used.
Here, then, is the interface of the class Person_dbase
:
class Person_dbase { public: enum Listtype { list_by_length, list_by_weight, list_by_name, list_by_city, list_by_birthday, }; // ... constructors etc. void list(Listtype type); // list the information private: struct CmpPerson { union Compare_function { int (*persons)// comparing two Persons (Person const *p1, Person const *p2); int (*voids)// for qsort() (void const *p1, void const *p2); } cmp; union Person_accessor { char const *(Person::*cp)() const; int (Person::*i)() const; Date const &(Person::*d)() const; } pf; // to Person's access functions }; static CmpPerson cmpPerson[]; static Listtype selector; static int cmpstr(Person const *p1, Person const *p2); static int cmpint(Person const *p1, Person const *p2); static int cmpdate(Person const *p1, Person const *p2); Person *pp; // pointer to the info unsigned n; // number of persons stored. };
Next, we define each of the members of the Person_dbase
class
(as far as necessary).
list()
function now only has to do three things:
Listtype
parameter is copied to
selector
,
qsort()
is called. Note the
use of the cmpPerson
array to determine which compare
function to use.
Person
objects is
displayed. This part is left for the reader to implement.
void Person_dbase::list(Listtype type) { selector = type; qsort(pp, n, sizeof(Person), cmpPerson[type].cmp.voids); // list the sorted Person-database (to be implemented) }
cmpPerson[]
is a static array of CmpPerson
elements. In this example there are five different ways to sort
the data. Consequently, there are five elements in the array
cmpPerson[]
. All these elements can be defined and initialized
by the compiler. No run-time execution time is needed for this.
However, note the form of the declaration: the array is defined in
the scope of the Person_dbase
class. Its elements are
CmpPerson
s, also defined in the scope of the Person_dbase
class. Hence the double mentioning of Person_dbase
.
Person_dbase::CmpPerson Person_dbase::cmpPerson[] = { { // compare- and access // function to compare length cmpint, Person::length, }, { // same for weight cmpint, Person::weight, }, { // same for name cmpstr, Person::name, }, { // same for city cmpstr, Person::city, }, { // same for Date cmpdate, Person::birthdate, }, };
The compare functions, being static functions, have access to the
cmpPerson[]
array and to the Listtype selector
variable. This
information is used by the compare functions to call the relevant
access member function
of the two Person
objects, pointed to by the parameters of the
compare functions.
For this, the pointer to member
operator
->*
is used. The element cmpPerson[selector]
contains the function pointers to the functions to be used:
they are the fields
pf
, variant cp, i
or d
. These fields
return a pointer to a particular access function of a Person
object.
Through these pointers the functions can be associated to a
particular Person
object using the pointer to member operator. This results in
expressions like:
p1->*cmpPerson[selector].pf.cp
By this time we have
the name (i.e., address) of an access function for a particular
Person
object. To call this function, parentheses are needed,
one set of parentheses to protect this expression from
desintegrating due to the
high priority of the second set of parentheses, which are
needed for the actual call of the function. Hence, we get:
(p1->*cmpPerson[selector].pf.cp)()
Finally, here are the three compare functions:
int Person_dbase::cmpstr(Person const *p1, Person const *p2) { return ( strcmp ( (p1->*cmpPerson[selector].pf.cp)(), (p2->*cmpPerson[selector].pf.cp)() ) ); } int Person_dbase::cmpint(Person const *p1, Person const *p2) { return ( (p1->*cmpPerson[selector].pf.i)() - (p2->*cmpPerson[selector].pf.i)() ); } int Person_dbase::cmpdate(Person const *p1, Person const *p2) { return ( (p1->*cmpPerson[selector].pf.d)() < (p2->*cmpPerson[selector].pf.d)() ? -1 : (p1->*cmpPerson[selector].pf.d)() > (p2->*cmpPerson[selector].pf.d)() ); }