Is this creating a anew pointer member of a class?

R

Ravi

#include <iostream>
using namespace std;

class X
{
public:
int i;
};


int main()
{
int X::*j = &X::i; // What is the logic behind this line?
// int X::j = &X::i; // This doesn't work
return 0;
}
 
B

Bo Persson

Ravi said:
#include <iostream>
using namespace std;

class X
{
public:
int i;
};


int main()
{
int X::*j = &X::i; // What is the logic behind this line?

It's a pointer-to-member, for a member of class X, X::*.

Don't know when it would be useful though.
// int X::j = &X::i; // This doesn't work

No, it is just nonsense.
return 0;
}



Bo Persson
 
J

James Kanze

#include <iostream>
using namespace std;
class X
{
public:
int i;
};
int main()
{
int X::*j = &X::i; // What is the logic behind this line?

What exactly don't you understand about it? It's a declaration,
which is also a definition. It defines a variable j, with type
int X::*, initialized with the address of member X::i. It
contains 10 tokens: `int', `X', `::*', `j', `=', `&', `X', `::',
`i' and `;'.
// int X::j = &X::i; // This doesn't work

Why should it work? What is it supposed to mean? It starts
out looking like a definition, except that a definition with a
qualified name where the qualifier is a class is only legal at
namespace scope and if the class contains a declaration of the
variable as static. In other words, `int X::j' would only be
legal at namespace scope, and only if X contained `static int
j'. After that, you try to initialize it with a pointer to
member, where as it has type int.
 
J

James Kanze

It would help if you'd quote correctly, so we knew what you're
actually talking about.
maybe I'm using a compiler which is some 10 years old and
therefore doesn't recognize this construct, but I do not too.

I doubt it has anything to do with the age of the compiler.
It's been around since the beginning.
First, the variable "i" declared inside X as int is not
static, therefore X::i has no meaning to me and neither &X::i.

Why not? Both have meaning, regardless of whether the i is
static or not. Just about any C++ text should have talked about
X::i; it's a common idiom (although more often used with
functions, e.g. X::f()). Pointers to members are a bit more
exotic, but a good explination of the language will at least
mention them.
Second, I would expect on the left side of the equal a type,
plus an identifier name, but "int X::*" is not a type to me.

It is to the language.
Can you explain please ?

Explain what? In the expression X::i, the :: is a scope
resolution operator; it limits lookup and name resolution to
things that are visible in the scope of X (basically, X and its
base classes). Probably the most frequent use of this operator
(at least with a class on the left side) is to call a base class
function which we are extending, e.g.:

class Base
{
public:
virtual void f() ;
} ;

class Derived
{
public:
virtual void f() { Base::f() ; ... }
} ;

(I don't particularly like this idiom, but it is widespread.)
But it also works with variables, provided the variable is
accessible in the scope it is being used, e.g.

struct Base
{
int i ;
} ;
struct Derived
{
int i ;
} ;
Derived* pi ;
pi->Base::i = 3 ;

assigns to the i in the base class.


In the declaration, ::* signifies pointer to (non-static)
member. Syntactically, it works more or less like * for
pointer, except that you have to say member of what. So the
syntax is <classname> ::* (two tokens, except that the class
name can be qualified, and thus more than one token). A pointer
to member isn't really a pointer in the classical sense; it's a
selector which allows us to access a dynamically chosen member,
given an object of the class type, e.g.:

struct S
{
int i ;
int j ;
int k ;
} ;

S* p = new S ;
int S::*pm = &S::i ;
p->*pm = 1 ; // equivalent to p->i = 1
pm = &S::j ;
p->*pm = 2 ; // equivalent to p->j = 2
pm = &S::k ;
p->*pm = 3 ; // equivalent to p->k = 3

I'd say that pointers to members are fairly rare, but I've used
them once or twice (in over 15 years C++).

Note that outside of S, the expression &i has no meaning. If
you take the address of a qualified name, however, such as
&S::i, the result isn't a simple pointer (int*), but a pointer
to member (int S::*), which requires an instance to S in order
to be used. (Practically, except for comparison and assignment,
it is only valid on the right side of a pointer to member
operator, i.e. object.*i or pointer->*i.)
 
J

James Kanze

James Kanze ha scritto:
Right. I've retried today and it seems to work. Don't know
what I mistyped yesterday. :)
Never seen, but I did not read Stroustrup's book front to back.

As I said, pointers to members aren't that common, and I imagine
some (a lot?) of introductary texts skip them.
Well, I've investigated a bit to understand what was going on
under the hood. Apparently the declaration:
int X::*j = &X::i;
seems to add a new member to the class,

What makes you say that? There is no way to change a class once
it has been compiled. At all.
but when I issued a sizeof(X) before and after the
declaration, I got the same result; no new room was allocated
for j.

There is no member j. The variable j is a pointer to a member;
in fact, a selector. Under the hood, pointers to data members
are usually just integers with the offset of the object.
(Pointers to member functions are more complicated, since they
have to work with virtual functions as well.)
So I thought of X::*j as an alias (which takes no space) like:
int x;
int &y = x;

In this sense, it is very much like a pointer:

int x ;
int* y = &x ;

does NOT create a new int; it just gives you a means of
accessing an existing int.
but I then realized that the compiler can handle run time
assignements like:
int X::*j = (n==1 ? &X::i : &X::k);
that cannot of course be evaluated at compile time. So, he
has to make room for the pointer somewhere...

The pointer is the variable j.
I wrote a few lines of code to test how far I can go before
the compiler gives up:
========== cut ==========
#include <iostream.h>
class X
{
public:
int i;
int k;
};
int main(int argc, char* argv[])
{
X a,b; // two objects with two different addresses
a.i = 1;
a.k = -1;
b.i = 2;
b.k = -2;
for(int n=0;n<2;n++) {
int X::*j = (n==1 ? &X::i : &X::k);
cout << "a.*j=" << a.*j << endl;
cout << "b.*j=" << b.*j << endl;
}
cout << "sizeof(a)=" << sizeof(a) << endl;
cout << "sizeof(b)=" << sizeof(b) << endl;
return 0;
}
========== cut ==========
Surprisingly (to me :) the output is as expected:

How can what's expected be "surprisingly".
sizeof(a)=8
sizeof(b)=8
a.*j=-1
b.*j=-2
a.*j=1
b.*j=2
sizeof(a)=8
sizeof(b)=8
So I wondered where and how much storage for X::*j has
actually allocated.
I eventually dug into the assembly generated by the compiler
to realize that
int X::*j = ...
is actually allocated onto the stack as one single pointer and
that in that place the compiler stores not an absolute address
(which doesn't exists) but an address relative to the object
head.

It's a local variable, so where else would it be allocated?
In this way, he can use only one address to compute both: a.*j
and b.*j, although they actually reference two different
memory locations.
To sum things up, X::*j qualifies as a pointer to some type,
but it's not exactly such a thing; for example you cannot
retrieve the address it is pointing to.

Exactly. It's a relative pointer, if you prefer. I prefer to
think of it as a selector. The name you give it doesn't matter,
however. The important thing is that it only works with an
object; it and the object together must be used to access
anything.
What I wasn't understanding was how a single pointer can point
to some specific class member (the integer i) of every
instance of X, if all instances do have, in general, different
memory addresses (that's why I objected at first that i had to
be static).
The answer however is: "int X::*j" is not a pointer but an
offset.

Exactly. Now take a look at pointers to member functions, if
you really want to get confused.
 
J

James Kanze

[...]
Pardon me for jumping in, but what exactly led you to that
conclusion?
It's a pointer-to-member-function. Such pointers are
typically 2x the size of other pointers, since each one
potentially holds both a pointer to a vtable, and an offset
into that vtable.

Look again. In this example, it's a pointer to member data.

There exist several strategies for pointer to member functions.
In the simplest to understand, the "pointer" has three fields:
the address of a non-virtual function, the index in the vtable
of a virtual function, and a "fix-up" to be applied to the this
pointer (if e.g. the actual function is in a base class which
doesn't have the same starting address). Only one of the first
two will actually be used; a sentinal value will be used in one
of the fields to indicate that it isn't valid, and the other
field should be used. One simple optimization would be to use
the actual offset into the vtable, rather than the index; on
most machines, neither the address of a function nor the offset
of a pointer to a function can be odd, so this leaves a bit free
to flag which it is. And a lot of machines use thunks, the
compiler will generate a special function which will do the
necessary fixing up and call the virtual function, if the
assigned function is virtual. And there are certainly other
schemes I'm not familiar with.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,756
Messages
2,569,533
Members
45,007
Latest member
OrderFitnessKetoCapsules

Latest Threads

Top