value structs: what do I pass as a parameter to a method so they can be changed?

P

Peter Oliphant

This is possibly some other problem, but here goes anyway. I created a
'value struct'. I then created a stack semantic instance of it. I then
created a method that takes a pointer to such an instance, and changed one
of its components. I then tried this code, and it didn't change the
instance's component. Code is something like this:

value struct vStruct
{
int x ;
} ;

namespace MyGlobal
{
void Zero_vStruct( vStruct^ vs ) { vs->x = 0 ; }
} ;

void main()
{
vStruct vs ;
vs.x = 58 ;
MyGlobal::Zero_vStruct( %vs ) ;
int vsx = vs.x ; //still 58, not 0 !!!
return 0 ;
} ;

If I change vStruct to a 'ref struct' it works, but then I can't create an
array of such values without it being an array of pointers to such
instances. That is:

typedef array<vStruct> vStruct_Array_A ; //illegal if ref, legal if value
typedef array<vStruct^> vStruct_Array_B ; // always legal

So, I'm asking this: what do I pass to a method as a parameter so I can
change the component values of an instance of a value struct?

For context, I'm using VS C++.NET 2005 using clr/:pure syntax...

Thanks in advance for reponses! : )

[==P==]
 
T

Tamas Demjen

Peter said:
value struct vStruct
{
int x ;
} ;

namespace MyGlobal
{
void Zero_vStruct( vStruct^ vs ) { vs->x = 0 ; }
} ;

void main()
{
vStruct vs ;
vs.x = 58 ;
MyGlobal::Zero_vStruct( %vs ) ;
int vsx = vs.x ; //still 58, not 0 !!!

What happens there is automatic boxing. Your vStruct is boxed into a
temporary ref class, which is independent from vs. Here's a simpler example:

void main()
{
vStruct vs;
vs.x = 58;
vStruct^ pvs = %vs;
pvs->x = 0;
}

If you try to debug it, you'll see that "pvs" has "[vStruct]" inside,
and x is indeed set to 0, but pvs is an instance completely detached
from vs. When you zero pvs, vs is not getting zeroed.

So what is exactly pvs? It is a ref class that wraps a value class, a
*copy* of vs. So pvs is not a pointer to vs, but a new ref class copy
allocated with gcnew. When you write

vStruct^ pvs = %vs;

the compiler generates something that is roughly equivalent to this:

ref struct vStructWrapper
{
vStruct anonymous;
};

vStructWrapper^ pvs = gcnew vStructWrapper;
pvs->anonymous = vs;

pvs is absolutely not related to vs from that point.

Why is that? Because the caret (^) symbol doesn't mean "the address of a
chunk of memory", but rather it means "a handle to a ref class", and it
must always be a ref class, on the heap, not on the stack.

What is the solution? Use references instead:

void Zero_vStruct( vStruct% vs ) { vs.x = 0; }

This will work fine.

Tom
 
T

Tamas Demjen

Peter Oliphant wrote:

Here is one more way of explaining it. Here
void Zero_vStruct( vStruct^ vs ) { vs->x = 0 ; }

vStruct^ is a gabage collected handle.

Here
vStruct vs ;
vs.x = 58 ;

vs is an object on the stack. There's no way to convert a stack object
to a GC handle, so the compiler boxes your value.

You can argue that it's confusing, and that the compiler shouldn't do
automatic boxing. You could say that something like this would be better:

vStrcut vs;
vStruct^ boxed = cli::box_cast<vStruct^>(vs); // imaginary code

and you would prefer if the following erred out:

vStruct^ boxed = %vs;

Although I didn't think this over that much. I think once you get used
to it, it will make sense. I'm sure the compiler team has spent a lot of
time thinking this over, and conculded that automatic boxing was a
reasonable solution.

Tom
 

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