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: 5.2.0a) and please state the paragraph you're referring to.
All mail received is seriously considered, and new (sub)releases of the Annotations will normally reflect your suggestions for improvements. Except for the incidental case I will not otherwise acknowledge the receipt of suggestions for improvements. Please don't misinterpret this for lack of appreciation.
Classes having pointer data members
have been discussed in detail in
chapter 6. 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 delete 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. In this chapter these special pointers are the topic of discussion.
class String { char const *(*d_sp)() const; public: char const *get() const; };For this class, it is not possible to define a
char const *(*d_sp)()
const
pointing to the get()
member function of the String
class:
d_sp
cannot be given the address of the member function get()
.
One of the reasons why this doesn't work is that the variable d_sp
has
a
global scope, while the member function get()
is defined within the
String
class, and has class scope
. The fact that the variable d_sp
is part of the String
class is of no relevance. According to d_sp
's
definition, it points to a function living 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::*d_sp)() const;So, due to the
String::
prefix, d_sp
is defined to be active only
in the context of the class String
. Here, it is defined as a pointer to a
constant function, not expecting arguments, and returning a pointer to
constant characters.
char const * (String::*d_sp)() const
to indicate:
d_sp
is a pointer (*d_sp
),
String
(String::*d_sp
).
const
function, returning a char const *
:
char const * (String::*d_sp)() const
char const *String::somefun() const;
a const
parameterless function in the class String
, returning a
char const *
.
char const * ( String::somefun ) () const
*
)) character immediately before the
function-name itself:
char const * ( String:: * somefun ) () const
char const * (String::*d_sp)() const
String
contains a string text
member. How to construct a pointer to
this member? Again we follow the basic procedure:
string (String::text)
*
)) character immediately before the
variable-name itself:
string (String::*text)
string (String::*tp)
In this case, the parentheses are superfluous and may be omitted:
string String::*tp
char const * (*sp)() const
char const * (String::*d_sp)() const
String
class itself. The pointer to a member may be defined
in the class (so it becomes a data member itself), or in another class, or as
a local or global variable. In all of these cases the pointer to member
variable can be given the address of the kind of member it points to. The
important part is that a pointer to member can be initialized or assigned
without the need for an object of the corresponding class.
Initializing or assigning an address to such a pointer does nothing but indicating to which member the pointer will point to. This addressing can be considered a relative address: relative to the first byte used by an object. Consequently, no object is required when pointers to members are initialized or assigned. On the other hand, while it is allowed to initialize or assign a pointer to member, it is (of course) not possible to access these members without an associated object.
In the following example
initialization of and assignment to
pointers to members is illustrated (for illustration purposes all members of
PointerDemo
are defined public
):
class PointerDemo { public: unsigned get() const { return value; } unsigned value; }; int main() { // initialization unsigned (PointerDemo::*getPtr)() = &PointerDemo::get; unsigned PointerDemo::*valuePtr = &PointerDemo::value; getPtr = &PointerDemo::get; // assignment valuePtr = &PointerDemo::value; }Note that actually nothing special in involved: the difference with pointers at global scope is that we're now restricting ourselves to the scope of the
PointerDemo
class. Because of this restriction all
pointer definitions and all variables whose addresses are used must now be
given the PointerDemo
class scope.
*
is used to reach the object or value the pointer points at.
With pointers to objects the
field selector operator operating on pointers
(
->
) or the field selector operating operating on objects (.
) can be
used to select appropriate members.
To use a pointer to a member with an object the
pointer to member field selector (
.*
) must be used. To use a
pointer to a member via a pointer to an object the `pointer to member field
selector through a pointer to an object' (
->*
) must be used. These two
operators combine the notions of, on the one hand, a field selection (the
.
and ->
parts) to reach the appropriate field in an object and, on
the second hand, the notion of dereferencing: A
dereference operation is
used to reach the function or variable the pointer to member points at.
Using the example from the previous section, let's see how we can use the pointer to member function and the pointer to data member:
#include <iostream> class PointerDemo { public: unsigned get() const { return value; } unsigned value; }; int main() { // initialization unsigned (PointerDemo::*getPtr)() = &PointerDemo::get; unsigned PointerDemo::*valuePtr = &PointerDemo::value; PointerDemo object, // (1) (see text) *ptr = &object; object.*valuePtr = 12345; // (2) cout << object.*valuePtr << endl; cout << object.value << endl; ptr->*valuePtr = 54321; // (3) cout << object.value << endl; cout << (object.*getPtr)() << endl; // (4) cout << (ptr->*getPtr)() << endl; }We note:
PointerDemo
object and a pointer to such an
object is defined.
.*
operator,
to reach the member valuePtr
points to. This member is given a value.
PointerDemo
object, and hence we use the
->*
operator.
.*
and ->*
are once again used, but this
time to call a function through a pointer to member. Realize that the function
argument list has a higher priority than pointer to member field selector
operator, so the latter must be protected by its own set of parentheses.
Person
from section
7.2. This class contains fields for a person's name, address and
phone. Let's assume we want to construct a Person
data base of emplyees.
The employee data base can be queried, by depending on the kind of person
querying the data base either the name, the name and phone number or all
stored information about the person is made available. This implies that a
member function like getAddress()
must return something like
`<not available>
' in cases where the person querying the data base is not
allowed to see the person's address, and the actual address in other cases.
Assume the emplyee data base is opened with an argument reflecting the
status of the employee who wants to make some queries. The status could
reflect his or her position in the organization, like BOARD
,
SUPERVISOR
, SALESPERSON
, or CLERK
. The first two categories are
allowed to all information about the employees, the SALESPERSON
are
allowed to see the emplyee's phone numbers, while the CLERK
is only
allowed to see whether a person is actually a member of the organization.
We now construct a member string getPersonInfo(char const *name)
in the
data base class. A standard implementation of this class could be:
string PersonData::getPersonInfo(char const *name) { Person *p = lookup(name); // see if `name' exists if (!p) return "not found"; switch (category) { case BOARD: case SUPERVISOR: return allInfo(p); case SALESPERSON: return noPhone(p); case CLERK: return nameOnly(p); } }Although it doesn't take much time, the
switch
must be evaluated every
time getPersonCode()
is called, and similar constructions are required in
other functions that might show behavior conditional to the value of the
category
variable.
However, we can also define a member infoPtr
as a pointer to a
member function of the class PersonData
returning a string
and
expecting a Person
pointer as its argument. Note that this pointer can now
be used to point to allInfo()
, noPhone()
or
nameOnly()
. Furthermore, the function that the pointer variable points to
will be known by the time the PersonData
object is constructed, as the
employee status is given as an argument to the constructor of the
PersonData
object.
After having set the infoPtr
member to the appropriate member
function, the getPersonInfo()
member function may now be rewritten:
string PersonData::getPersonInfo(char const *name) { Person *p = lookup(name); // see if `name' exists return p ? (this->*infoPtr)(p) : "not found"; }Note the construction that is used for accessing a pointer to member within a class:
this->*infoPtr
.
The member infoPtr
is defined as follows (within the class
PersonData
, omitting other members):
class PersonData { private: string (PersonData::*infoPtr)(Person *p); };Finally, the constructor must initialize
infoPtr
to point to the
currect member function. The constructor could, for example, be given the
following code (showing only the pertinent code):
PersonData::PersonData(PersonData::EmplyeeCategory cat) { switch (cat) { case BOARD: case SUPERVISOR: infoPtr = &PersonData::allInfo; case SALESPERSON: infoPtr = &PersonData::noPhone; case CLERK: infoPtr = &PersonData::nameOnly; } }Note the way the addresses of the member functions are taken: the class
PersonData
scope must be specified, even though we're already inside
a member function of the class PersonData
.
An example using
pointers to data members is given in section
17.4.60, in the context of the
stable_sort()
generic algorithm.
this
pointer. When these static members are
public, they can be accessed as a
global function, albeit that their class
names are required when they are called.
Assume that a class String
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; }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 are comparable to global functions and data, their addresses can be stored in ordinary pointer variables, operating at the global level. Actually, using a pointer to member to address a static member of a class would produce a compilation error.
For example, the address of a static member function int
String::n_strings()
can simply be stored in a variable int (*pfi)()
, 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() }