What's with the new C++/CLI pointers (handles ^)? Managed C++.NET

R

raylopez99

microsoft.public.dotnet.languages.vc

Please comment on the following code fragment. Note the comments
"Why"?

Particularly,
(1) why does referencing and dereferencing a pointer give the same
thing (at least in WriteLine(), which might be some sort of cast going
on behind the scenes),
(2) why doesn't the pointer "stick to" a reference, such as when y =
intRef; below, then intRef is changed but the y handle does not change,
and
(3) why doesn't y = &intVT compile?

Thanks

RL

///////////////////////////////////////////////////////////////
// in managed C++/CLI console mode, using Visual Studio 2005, C++.NET

int intVT = 10;
int %intRef = intVT;
int ^y = gcnew int(200);
Console::WriteLine("Should be 200 {0} EITHER WAY !: {1} ", y,
*y);

*y = 210; //also works y = 210; //WHY y, *y same?

Console::WriteLine("Should be 210 {0}...is it? (Yes!)",*y);

y = intRef;
Console::WriteLine("Should be intRef or 10: {0}...is it?
(Yes!)",y);

intRef = 21;
Console::WriteLine("Should be intRef or 21: {0} , {1}...is it?
(NO!)",y, intRef); //WHY NOT?

y = intRef; //but now it should work: add-- y = intRef; // also
works:
*y = intRef; // same answer of 21! //Why same answer (why y, *y same)?

Console::WriteLine("Should be intRef or 21: {0} , {1}...is it?
(Yes!)",y, intRef);

//y = &intVT; //unsafe operation by definition? (NO, illegal!
Won't compile!)
//Why won't compile?
// EOF
/////////////////////////////////////////////////////////////////////////////////
 
C

Carl Daniel [VC++ MVP]

raylopez99 said:
microsoft.public.dotnet.languages.vc

Please comment on the following code fragment. Note the comments
"Why"?

Particularly,
(1) why does referencing and dereferencing a pointer give the same
thing (at least in WriteLine(), which might be some sort of cast going
on behind the scenes),

Because there's no way to "see" the value of a tracking pointer. Rather,
referring to a tracking pointer variable by name will impliciutly
dereference the pointer, just like referring to an ordinary C++ reference
would.
(2) why doesn't the pointer "stick to" a reference, such as when y =
intRef; below, then intRef is changed but the y handle does not
change,
and

Because a tracking pointer always points to an object in the managed heap.
When y = intRef is executed, the current value of intVT is boxed and placed
on the gc heap and y is set to reference that boxed copy.
(3) why doesn't y = &intVT compile?

Because a tracking pointer always points to an object in the managed heap.
intVT is not on the managed heap, so an int^ can't point to it.

-cd
 
A

adebaene

microsoft.public.dotnet.languages.vc
See comments inline
///////////////////////////////////////////////////////////////
// in managed C++/CLI console mode, using Visual Studio 2005, C++.NET

int intVT = 10;
int %intRef = intVT;
Here we have a tracking reference that is "pointing" to a local
variable (on the stack)
int ^y = gcnew int(200);
Console::WriteLine("Should be 200 {0} EITHER WAY !: {1} ", y, *y);

When passing y to WriteLine, you are passing a handle to an object that
is on the garbage-collected heap (an Int32^). Since Int32 implements
IFormattable, WriteLine will call IFormattable::ToString on this
object.
When passing *y to WriteLine, you are implcitely unboxing the object,
and therefore passing an int to WriteLine, who knows how to handle it.

*y = 210; //also works y = 210; //WHY y, *y same?
y=210 works because there will be no meaning in changing the value of a
handle (remember, in .NET, handles are OPAQUES : there is no way to
assign them directly a value as you can do with pointers). In that
case, the compiler chooses the alternate interpretation instead of
spiting an error. I would say this choice is debatable, but this is how
things are....
Console::WriteLine("Should be 210 {0}...is it? (Yes!)",*y);

y = intRef;
Here is the danger! Since y is an Int32^, it can only "points" to
object on the managed heap. This line implicitely boxes the intRef
value : An In32 is gcnew-ed on the the managed heap and is assigned the
intRef value. This is a COPY of the original value. See "Implicit
boxing" in MSDN for more details.

Console::WriteLine("Should be intRef or 10: {0}...is it? (Yes!)",y);
See my explanation concerning the first WriteLine call....
intRef = 21;
Console::WriteLine("Should be intRef or 21: {0} , {1}...is it? (NO!)",y, intRef); //WHY NOT?
See above : y points to a COPY of intRef.
y = intRef; //but now it should work: add-- y = intRef; // also works:

Here again, since "y" has no possible meaning, the compiler interprets
it as "µy" (which I feel is dubious)
*y = intRef; // same answer of 21! //Why same answer (why y, *y same)?

Console::WriteLine("Should be intRef or 21: {0} , {1}...is it?> (Yes!)",y, intRef);

//y = &intVT; //unsafe operation by definition? (NO, illegal! Won't compile!)
//Why won't compile?

A handle must "point" to an object on the garbage-collected heap, so
it can't be assigned the address of a stack object.

Arnaud
MVP - VC
 
R

raylopez99

Excellente! I appreciate it very much, both of the replies, and will
try and live within the confines of handles, garbage collection and
whatever else C++.NET throws my way (until I get fed up with it; I'm
also learning C#.NET, which seems a bit more simple/simplistic).
RL
 
B

Ben Voigt

The reason:
int ^y;
y = xyz;
*y = xyz;

work the same way is: they don't!

Microsoft has provided default definitions (for all built-in types) of:
int^ operator= (int^%, int^)
int operator= (int%, int)
and implicit boxing of the right-hand side...

But I think you'll find that:
int ^x = 1;
int ^y = 1;
^y = 2; // places 2 in the box referenced by y, changing also x
y = 3; // boxes 3, makes y point to it, leaving x pointing to boxed 2

Also, for user-defined types you can actually define those very differently.
To overload the handle operators, you have to use a static member function,
while for normal C++ operators, you can use a static member, instance
member, or friend function. On second thought, I'm not sure you can
redefine operator= on handle types, but you definitely can make the
arithmetic operators act differently for handles vs non-handles.
 
R

raylopez99

Thanks Ben for your input, but I just compiled your suggested code and
found a few mistakes (see below).

Ray

////////////////

// from the net:

int ^x = 1;
int ^y = 1;
Console::WriteLine("x, y are one,one: {0}, {1} ", *x, *y); //gives
output 1,1
*y = 2; // not ^y=2; places 2 in the box referenced by y, changing
also x [not true, x is not changed but remains one]
Console::WriteLine("x, y now two are one,two: {0}, {1} ", *x, *y);
//gives output 1,2
y = 3; // boxes 3, makes y point to it, leaving x pointing to boxed 2
[Not true, x points to 1]
Console::WriteLine("x, y are three: {0}, {1} ", *x, *y); //gives output
1,3, as expected

//////////////////

==
 
B

Ben Voigt

raylopez99 said:
Thanks Ben for your input, but I just compiled your suggested code and
found a few mistakes (see below).

Yup, I don't always think right.

The part about matching different operator overrides is definitely true
though. For some class I ended up having to define both sets.
Ray

////////////////

// from the net:

int ^x = 1;
int ^y = 1;
Console::WriteLine("x, y are one,one: {0}, {1} ", *x, *y); //gives
output 1,1
*y = 2; // not ^y=2; places 2 in the box referenced by y, changing
also x [not true, x is not changed but remains one]
Console::WriteLine("x, y now two are one,two: {0}, {1} ", *x, *y);
//gives output 1,2
y = 3; // boxes 3, makes y point to it, leaving x pointing to boxed 2
[Not true, x points to 1]
Console::WriteLine("x, y are three: {0}, {1} ", *x, *y); //gives output
1,3, as expected

//////////////////

==
Ben said:
The reason:
int ^y;
y = xyz;
*y = xyz;

work the same way is: they don't!

Microsoft has provided default definitions (for all built-in types) of:
int^ operator= (int^%, int^)
int operator= (int%, int)
and implicit boxing of the right-hand side...

But I think you'll find that:
int ^x = 1;
int ^y = 1;
^y = 2; // places 2 in the box referenced by y, changing also x
y = 3; // boxes 3, makes y point to it, leaving x pointing to boxed 2

Also, for user-defined types you can actually define those very
differently.
To overload the handle operators, you have to use a static member
function,
while for normal C++ operators, you can use a static member, instance
member, or friend function. On second thought, I'm not sure you can
redefine operator= on handle types, but you definitely can make the
arithmetic operators act differently for handles vs non-handles.
 
R

raylopez99

Thanks Ben. I'm sure even thinking non-right you're way ahead of me,
since I only play around with programming for fun...right now I'm
playing around with a demonstration program using the 'this' pointer
and using it to instantiate another composition class that's also a
friend (just to see what's different, if anything, between C++ and
C++.NET/CLI 2005 version).

Feel free to post an example of "matching different operator overrides
is definitely true .... For some class I ended up having to define
both sets." (operator overides use the 'this' pointer so it will be a
good example for my purposes).

RL
 
B

Ben Voigt

raylopez99 said:
Thanks Ben. I'm sure even thinking non-right you're way ahead of me,
since I only play around with programming for fun...right now I'm
playing around with a demonstration program using the 'this' pointer
and using it to instantiate another composition class that's also a
friend (just to see what's different, if anything, between C++ and
C++.NET/CLI 2005 version).

Feel free to post an example of "matching different operator overrides
is definitely true .... For some class I ended up having to define
both sets." (operator overides use the 'this' pointer so it will be a
good example for my purposes).

Again, not compiled so you may have to tweak it a bit:

ref struct Fraction
{
int Numerator, int Denominator;

public Fraction(int num, int denom) { /* reduces fraction, etc. */ }

public static Fraction^ operator*(Fraction^ one, Fraction^ two) {
return gcnew Fraction(one->Numerator * two->Numerator,
one->Denominator * two->Denominator);
}
public Fraction% operator*(const Fraction% two) {
return Fraction(Numerator * two.Numerator, Denominator *
two.Denominator);
}
};

client code:
void TestIt()
{
Fraction a, b; // erm, C# needs = new Fraction() added here
a.Numerator = 1;
a.Denominator = 3;
b.Numerator = 2;
b.Denominator = 7;
Fraction product = a * b; // C# and C++/CLI call different operators!
// In fact, C++/CLI has to
call the copy constructor as well
}

C++ code that acts like C#:
void TestIt()
{
Fraction^ a = gcnew Fraction();
Fraction^ b = gcnew Fraction();
a->Numerator = 1;
a->Denominator = 3;
b->Numerator = 2;
b->Denominator = 7;
Fraction^ product = a * b; // Calls the same operator* as C# does
}
 
R

raylopez99

Thanks; this will be my next demo project. Today I learned
Delegates/Events (like function pointers/ templates but a little
different) in managed C++/CLI, and found out much to my chagrin no
friend functions exist (but no problem, just declare and use a public
interface between the friendly classes; a bit ackward but no big deal).

RL
 

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

Top