1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
|
hi(size: pointer to member)hi(pointer: to member: size) An interesting
characteristic of pointers to members is that their sizes differ from those of
`normal' pointers. Consider the following little program:
verbinclude(-a examples/size.cc)
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:
verb( printf("%p", &X::fun);)
Of course, tt(printf) is likely not the right tool for displaying the
value of these bf(C++) specific pointers. The values of these pointers can be
inserted into streams when a tt(union), reinterpreting the 8-byte pointers as
a series of size_t tt(char) values, is used:
verbinclude(-a examples/union.cc)
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
tt(std::fstream). It is derived from tt(std::ifstream) and
tt(std::ofstream). An tt(fstream), therefore, contains both an tt(ifstream) and
an tt(ofstream). An tt(fstream) will be organized as shown in figure
ref(PMSIZES).
figure(pointermembers/sizes)
(std::fstream object organization)
(PMSIZES)
In tt(fstream (a)) the first base class was tt(std::istream), and the second
baseclass was tt(std::ofstream). But it could also very well be the other way
around, as illustrated in tt(fstream (b)): first the tt(std::ofstream), then
the tt(std::ifstream). And that's the crux of the biscuit.
If we have an tt(fstream fstr{"myfile"}) object and do tt(fstr.seekg(0)), then
we call tt(ifstream's seekg) function. But if we do tt(fstr.seekp(0)), then we
call tt(ofstream's seekp) function. These functions have their own addresses,
say &seekg and &seekp. But when we call a member function (like
tt(fstr.seekp(0))) then what we in fact are doing is tt(seekp(&fstr, 0)).
But the problem here is that tt(&fstr) does not represent the correct object
address: tt(seekp) operates on an tt(ofstream), and that object does not start
at tt(&fstr), so (in tt(fstream (a))), at tt(&(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
verb( ostream &(fstream::*ptr)(ios::off_type step, ios::seekdir org) = &seekp;)
and then do tt((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):
verb( 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 tt(fstream), which embeds tt(ifstream) and tt(ofstream)):
verb( struct C: public A, public B
{};)
Next, in tt(main) we define objects of two different unions and assign the
address of tt(B::bfun) to their tt(ptr) fields, but tt(BPTR.ptr) looks at it
as a member in the tt(struct B) world, while tt(CPTR.ptr) looks at it as a
member in the tt(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):
verb( 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
verb( 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 `tt(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.
|