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 of 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 (in the next line) 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 { std::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
.
Since the EmployeeCategory
values are known, the switch
in the
above constructor can also easily be avoided by defining a static array of
pointers to functions. The class PersonData
defines the static array:
class PersonData { std::string (PersonData::*d_infoPtr)(Person *p); static std::string (PersonData::*s_infoPtr[])(Person *p); };
and s_infoPtr[]
can be initialized compile-time:
string (PersonData::*PersonData::s_infoPtr[])(Person *p) = { &PersonData::allInfo, // BOARD &PersonData::allInfo, // SUPERVISOR &PersonData::noPhone, // SALESPERSON &PersonData::nameOnly // CLERK };
The constructor, instead of using a switch
, now directly calls the
required member from the appropriate array element:
PersonData::PersonData(PersonData::EmployeeCategory cat) : d_infoPtr(s_infoPtr[cat]) {}
An example using pointers to data members is provided in section
19.1.61, 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 for displaying 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'; }
But why are their sizes different from the sizes of ordinary pointers? To
answer this question let's first have a look at the familiar
std::fstream
. It is derived from std::ifstream
and
std::ofstream
. An fstream
, therefore, contains both an ifstream
and
an ofstream
. An fstream
will be organized as shown in figure
23.
In fstream (a)
the first base class was std::istream
, and the second
baseclass was std::ofstream
. But it could also very well be the other way
around, as illustrated in fstream (b)
: first the std::ofstream
, then
the std::ifstream
. And that's the crux of the biscuit.
If we have an fstream fstr{"myfile"}
object and do fstr.seekg(0)
, then
we call ifstream's seekg
function. But if we do fstr.seekp(0)
, then we
call ofstream's seekp
function. These functions have their own addresses,
say &seekg and &seekp. But when we call a member function (like
fstr.seekp(0)
) then what we in fact are doing is seekp(&fstr, 0)
.
But the problem here is that &fstr
does not represent the correct object
address: seekp
operates on an ofstream
, and that object does not start
at &fstr
, so (in fstream (a)
), at &(fstr + sizeof(ifstream))
.
So, the compiler, when calling a member function of a class using inheritance, must make a correction for the relative location of an object whose members we are calling.
However, when we're defining something like
ostream &(fstream::*ptr)(ios::off_type step, ios::seekdir org) = &seekp;
and then do (fstr->*)ptr(0)
the compiler doesn't know anymore which
function is actually being called: it merely receives the function's
address. To solve the compiler's problem the shift (for the location of the
ofstream object) is now stored in the member pointer itself. That's one reason
why the extra data field is needed when using function pointers.
Here is a concrete illustration: first we define 2 structs, each having a member function (all inline, using single line implementations to save some space):
struct A { int a; }; struct B { int b; void bfun() {} };
Then we define C, which is derived from both A (first) and B (next)
(comparable to fstream
, which embeds ifstream
and ofstream
):
struct C: public A, public B {};
Next, in main
we define objects of two different unions and assign the
address of B::bfun
to their ptr
fields, but BPTR.ptr
looks at it
as a member in the struct B
world, while CPTR.ptr
looks at it as a
member in the struct C
world.
Once the unions' pointer fields have been assigned their value[] arrays are used to display the content of the ptr fields (see below):
int main() { union BPTR { void (B::*ptr)(); unsigned long value[2]; }; BPTR bp; bp.ptr = &B::bfun; cout << hex << bp.value[0] << ' ' << bp.value[1] << dec << '\n'; union CPTR { void (C::*ptr)(); unsigned long value[2]; }; CPTR cp; cp.ptr = &C::bfun; cout << hex << cp.value[0] << ' ' << cp.value[1] << dec << '\n'; }
When this program is run, we see
400b0c 0 400b0c 4
(your address values (the first ones on the two lines) may differ). Note
that the functions' addresses are the same, but since in the C world the B
object lives beyond the A object, and the A object is 4 bytes large, we must
add 4 to the value of the `this
' pointer when calling the function from a C
object. That's exactly what the shift value in the pointer's second field is
telling the compiler.