Don't hesitate to send in feedback: send an e-mail if you like the C++ Annotations; if you think that important material was omitted; if you find errors or typos in the text or the code examples; or if you just feel like e-mailing. Send your e-mail to Frank B. Brokken.Please state the document version you're referring to, as found in the title (in this document: 7.3.0) and please state chapter and paragraph name or number you're referring to.
All received mail is processed conscientiously, and received suggestions for improvements will usually have been processed by the time a new version of the Annotations is released. Except for the incidental case I will normally not acknowledge the receipt of suggestions for improvements. Please don't interpret this as me not appreciating your efforts.
Classes having pointer data members
have been discussed in detail in
chapter 7. 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 let a
char const *(*d_sp)()
const
data member point 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
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 irrelevant. 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's 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 as a pointer only
in the context of the class String
. It is defined as a pointer to a
function in the class String
, not expecting arguments, not modifying its
object's data, 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 d_text
member. How to construct a
pointer to this member? Again we follow the basic procedure:
string (String::d_text)
*
)) character immediately before the
variable-name itself:
string (String::*d_text)
string (String::*tp)In this case, the parentheses are superfluous and may be omitted:
string String::*tp
char const * (*sp)() const;becomes a pointer to a member function after prefixing the class-scope:
char const * (String::*sp)() const;Nothing in the above discussion forces us to define these pointers to members in the
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 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. This can be considered a kind of relative address: relative to the object for which the function is called. 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
). In the example itself, note the use
of the &
-operator to determine the addresses of the members. These
operators, as well as the class-scopes are required. Even when used inside the
class member implementations themselves:
class PointerDemo { public: unsigned d_value; unsigned get() const; }; inline unsigned PointerDemo::get() const { return d_value; } int main() { // initialization unsigned (PointerDemo::*getPtr)() const = &PointerDemo::get; unsigned PointerDemo::*valuePtr = &PointerDemo::d_value; getPtr = &PointerDemo::get; // assignment valuePtr = &PointerDemo::d_value; }Actually, nothing special is 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 be given the
PointerDemo
class scope. Pointers to members can also be used with
virtual
member functions. No further changes are required if, e.g., get()
is
defined as a virtual member function.
*
is used to reach the object or value the pointer points to.
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 member in combination 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 other hand, the notion of dereferencing: a
dereference operation is
used to reach the function or variable the pointer to member points to.
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 d_value; unsigned get() const; }; inline unsigned PointerDemo::get() const { return d_value; } using namespace std; int main() { // initialization unsigned (PointerDemo::*getPtr)() const = &PointerDemo::get; unsigned PointerDemo::*valuePtr = &PointerDemo::d_value; PointerDemo object; // (1) (see text) PointerDemo *ptr = &object; object.*valuePtr = 12345; // (2) cout << object.*valuePtr << endl; cout << object.d_value << endl; ptr->*valuePtr = 54321; // (3) cout << object.d_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. Hence we use the
->*
operator.
.*
and ->*
are used once again, but this
time to call a function through a pointer to member. Since that the function
argument list has a higher priority than pointer to member field selector
operator, the latter must be protected by its own set of parentheses.
Person
from section
7.2. This class contains fields holding a person's name, address
and phone number. Let's assume we want to construct a Person
database of
employees. The employee database can be queried, but depending on the kind of
person querying the database 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 address()
must return something like `<not
available>
' in cases where the person querying the database is not allowed
to see the person's address, and the actual address in other cases.
Assume the employee database 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 see all information about the employees, a SALESPERSON
is
allowed to see the employee's phone numbers, while the CLERK
is only
allowed to verify whether a person is actually a member of the organization.
We now construct a member string personInfo(char const *name)
in the
database class. A standard implementation of this class could be:
string PersonData::personInfo(char const *name) { Person *p = lookup(name); // see if `name' exists if (!p) return "not found"; switch (d_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 nonetheless be
evaluated every time personCode()
is called. Instead of using a switch, we
could define a member d_infoPtr
as a pointer to a member function of the
class PersonData
returning a string
and expecting a Person
reference 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, assuming that the employee status is given as an
argument to the constructor of the PersonData
object.
After having set the d_infoPtr
member to the appropriate member
function, the personInfo()
member function may now be rewritten:
string PersonData::personInfo(char const *name) { Person *p = lookup(name); // see if `name' exists return p ? (this->*d_infoPtr)(p) : "not found"; }Note the syntactic construction when using a pointer to member from within a class:
this->*d_infoPtr
.
The member d_infoPtr
is defined as follows (within the class
PersonData
, omitting other members):
class PersonData { string (PersonData::*d_infoPtr)(Person *p); };Finally, the constructor must initialize
d_infoPtr
to point to the
correct member function. The constructor could, for example, be given the
following code (showing only the pertinent code, break
statements should
of course be inserted to prevent `falling though cases'):
PersonData::PersonData(PersonData::EmployeeCategory cat) { switch (cat) { case BOARD: case SUPERVISOR: d_infoPtr = &PersonData::allInfo; case SALESPERSON: d_infoPtr = &PersonData::noPhone; case CLERK: d_infoPtr = &PersonData::nameOnly; } }Note how addresses of member functions are determined: 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.
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 usually be accessed like global entities (but see section 10.2.1). 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 associated objects, 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)() = String::n_strings; // address of the static member function cout << (*pfi)() << endl; // print the value produced by String::n_strings() }
#include <string> #include <iostream> class X { public: void fun(); string d_str; }; inline void X::fun() { std::cout << "hello\n"; } using namespace std; int main() { cout << "size of pointer to data-member: " << sizeof(&X::d_str) << "\n" << "size of pointer to member function: " << sizeof(&X::fun) << "\n" << "size of pointer to non-member data: " << sizeof(char *) << "\n" << "size of pointer to free function: " << sizeof(&printf) << endl; } /* generated output: size of pointer to data-member: 4 size of pointer to member function: 8 size of pointer to non-member data: 4 size of pointer to free function: 4 */Note that the size of a pointer to a member function is eight bytes, whereas all other pointers are four bytes (Using the Gnu g++ compiler).
In general, these pointer sizes are not explicitly used, but their differing sizes may cause some confusion in statements like:
printf("%p", &X::fun);Of course,
printf()
is likely not the right tool to produce the
value of these C++ specific pointers. The values of these pointers can be
inserted into streams when a union
, reinterpreting the 8-byte pointers as
a series of size_t char
values, is used:
#include <string> #include <iostream> #include <iomanip> class X { public: void fun(); std::string d_str; }; inline void X::fun() { std::cout << "hello\n"; } using namespace std; int main() { union { void (X::*f)(); unsigned char *cp; } u = { &X::fun }; cout.fill('0'); cout << hex; for (unsigned idx = sizeof(void (X::*)()); idx-- > 0; ) cout << setw(2) << static_cast<unsigned>(u.cp[idx]); cout << endl; }