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: 10.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 are usually 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 9. Classes defining pointer data-members deserve some
special attention, as they usually require the definitions of copy
constructors, overloaded assignment operators and destructors
Situations exist where we do not need a pointer to an object but rather a pointer to members of a class. Pointers to members can profitably be used to configure the behavior of objects of classes. Depending on which member a pointer to a member points to objects will show certain behavior.
Although pointers to members have their use, polymorphism can frequently be
used to realize comparable behavior. Consider a class having a member
process
performing one of a series of alternate behaviors. Instead of
selecting the behavior of choice at object construction time the class could
use the interface of some (abstract) base class, passing an object of some
derived class to its constructor and could thus configure its behavior. This
allows for easy, extensible and flexible configuration, but access to the
class's data members would be less flexible and would possibly require the use
of `friend' declarations. In such cases pointers to members may actually be
preferred as this allows for (somewhat less flexible) configuration as well as
direct access to a class's data members.
So the choice apparently is between on the one hand ease of configuration and on the other hand ease of access to a class's data members. In this chapter we'll concentrate on pointers to members, investigating what these pointers have to offer.
class String { char const *(*d_sp)() const; public: char const *get() const; };For this class, it is not possible to let
char const *(*d_sp)() const
point to the String::get
member function as 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 (it is a pointer to a function, not a
pointer to a function within String
), while the member function get
is
defined within the String
class, and thus has class scope
. The fact
that d_sp
is a data member of the class String
is irrelevant
here. According to d_sp
's definition, it points to a function living
somewhere outside of the class.
Consequently, to define a pointer to a member (either data or function, but
usually a function) of a class, the scope of the pointer must indicate
class scope. Doing so, a pointer to the member
String::get
is defined like this:
char const *(String::*d_sp)() const;So, by prefixing the
*d_sp
pointer data member by String::
, it is
defined as a pointer in the context of the class String
. According to its
definition it is 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 that d_sp
*d_sp
);
String
(String::*d_sp
);
const
function, returning a char const *
(char const * (String::*d_sp)() const
).
The prototype of a matching function is therefore:
char const *String::somefun() const;which is any
const
parameterless function in the class
String
, returning a char const *
.
When defining pointers to members the standard procedure for constructing pointers to functions can still be applied:
char const * ( String::somefun ) () const
*
)) character immediately before the
function name itself:
char const * ( String:: * somefun ) () const
char const * (String::*d_sp)() const
Here is another example, defining a pointer
to a data member. Assume the class String
contains a string d_text
member. How to construct a pointer to this member? Again we follow standard
procedure:
std::string (String::d_text)
*
)) character immediately before the
variable-name itself:
std::string (String::*d_text)
std::string (String::*tp)In this case, the parentheses are superfluous and may be omitted:
string String::*tp
Alternatively, a very simple rule of thumb is
char const * (*sp)() const;becomes a pointer to a member function after prefixing the class-scope:
char const * (String::*sp)() const;Nothing forces us to define pointers to members in their target (
String
) classes. Pointers to members may be defined in their target
classes (so they become data members), or in another class, or as a local
variable or as a 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 requiring the existence an object of the pointer's target class.
Initializing or assigning an address to such a pointer merely indicates to which member the pointer points. This can be considered some 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. While it is allowed to initialize or assign a pointer to member, it is (of course) not possible to call those members without specifying an object of the correct type.
In the following example
initialization of and assignment to
pointers to members is illustrated (for illustration purposes all members of
the class PointerDemo
are defined public
). In the example itself the
&
-operator is used to determine the addresses of the members. These
operators as well as the class-scopes are required. Even when used inside
member implementations:
#include <cstddef> class PointerDemo { public: size_t d_value; size_t get() const; }; inline size_t PointerDemo::get() const { return d_value; } int main() { // initialization size_t (PointerDemo::*getPtr)() const = &PointerDemo::get; size_t PointerDemo::*valuePtr = &PointerDemo::d_value; getPtr = &PointerDemo::get; // assignment valuePtr = &PointerDemo::d_value; }
This involves nothing special. 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 special syntax is required when pointing to virtual
members. Pointer construction, initialization and assignment is done
identically to the way it is done with non-virtual members.
*
is used. 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 specified. 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 specified. These
two operators combine the notions of a field selection (the .
and ->
parts) to reach the appropriate field in an object and 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 pointers to member functions and pointers to data members:
#include <iostream> class PointerDemo { public: size_t d_value; size_t get() const; }; inline size_t PointerDemo::get() const { return d_value; } using namespace std; int main() { // initialization size_t (PointerDemo::*getPtr)() const = &PointerDemo::get; size_t PointerDemo::*valuePtr = &PointerDemo::d_value; PointerDemo object; // (1) (see text) PointerDemo *ptr = &object; object.*valuePtr = 12345; // (2) cout << object.*valuePtr << '\n' << object.d_value << '\n'; ptr->*valuePtr = 54321; // (3) cout << object.d_value << '\n' << (object.*getPtr)() << '\n' << // (4) (ptr->*getPtr)() << '\n'; }
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, this time to call
a function through a pointer to member. As the function argument list
has a higher priority than the pointer to member field selector
operator, the latter must be protected by parentheses.
Person
from section
9.3. Person
defines data members holding a person's name,
address and phone number. 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.
The employee database is opened specifying 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 personInfo
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 pointer to a
Person
as its argument.
Instead of evaluating the switch this pointer can be used to point to
allInfo
, noPhone
or nameOnly
. Furthermore, the member function
the pointer points to will be known by the time the PersonData
object is constructed and so its value needs to be determined only once (at
the PersonData object's construction time).
Having initialized d_infoPtr
the personInfo
member function is now
implemented simply as:
string PersonData::personInfo(char const *name) { Person *p = lookup(name); // see if `name' exists return p ? (this->*d_infoPtr)(p) : "not found"; }
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 initializes
d_infoPtr
. This could be realized
using a simple switch:
PersonData::PersonData(PersonData::EmployeeCategory cat) : switch (cat) { case BOARD: case SUPERVISOR: d_infoPtr = &PersonData::allInfo; break; case SALESPERSON: d_infoPtr = &PersonData::noPhone; break; case CLERK: d_infoPtr = &PersonData::nameOnly; break; } }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 provided in section
19.1.60, in the context of the stable_sort
generic algorithm.
Assume a class String
has a public static member function
count
, returning the number of string objects created so
far. Then, without using any String
object the function
String::count
may be called:
void fun() { cout << String::count() << '\n'; }Public static members can be called like free functions (but see also section 8.2.1). Private static members can only be called within the context of their class, by their class's member or friend functions.
Since static members have no associated objects their addresses can be stored in ordinary function pointer variables, operating at the global level. Pointers to members cannot be used to store addresses of static members. Example:
void fun() { size_t (*pf)() = String::count; // initialize pf with the address of a static member function cout << (*pf)() << '\n'; // displays the value returned by String::count() }
#include <string> #include <iostream> class X { public: void fun(); std::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) << '\n'; } /* generated output (on 32-bit architectures): 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 */
On a 32-bit architecture a pointer to a member function requires eight bytes, whereas other kind of pointers require four bytes (Using Gnu's g++ compiler).
Pointer sizes are hardly ever explicitly used, but their sizes may cause 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 << '\n'; }