How to: Use Tracking References in C++/CLI
This article shows how to use a tracking reference (%) in C++/CLI to pass common language runtime (CLR) types by reference.
To pass CLR types by reference
The following sample shows how to use a tracking reference to pass CLR types by reference.
// tracking_reference_handles.cpp
// compile with: /clr
using namespace System;
ref struct City {
private:
Int16 zip_;
public:
City (int zip) : zip_(zip) {};
property Int16 zip {
Int16 get(void) {
return zip_;
} // get
} // property
};
void passByRef (City ^% myCity) {
// cast required so this pointer in City struct is "const City"
if (myCity->zip == 20100)
Console::WriteLine("zip == 20100");
else
Console::WriteLine("zip != 20100");
}
ref class G {
public:
int i;
};
void Test(int % i) {
i++;
}
int main() {
G ^ g1 = gcnew G;
G ^% g2 = g1;
g1 -> i = 12;
Test(g2->i); // g2->i will be changed in Test2()
City ^ Milano = gcnew City(20100);
passByRef(Milano);
}
Output
zip == 20100
The next sample shows that taking the address of a tracking reference returns an interior_ptr (C++/CLI), and shows how to modify and access data through a tracking reference.
// tracking_reference_data.cpp
// compile with: /clr
using namespace System;
public ref class R {
public:
R(int i) : m_i(i) {
Console::WriteLine("ctor: R(int)");
}
int m_i;
};
class N {
public:
N(int i) : m_i (i) {
Console::WriteLine("ctor: N(int i)");
}
int m_i;
};
int main() {
R ^hr = gcnew R('r');
R ^%thr = hr;
N n('n');
N %tn = n;
// Declare interior pointers
interior_ptr<R^> iphr = &thr;
interior_ptr<N> ipn = &tn;
// Modify data through interior pointer
(*iphr)->m_i = 1; // (*iphr)->m_i == thr->m_i
ipn->m_i = 4; // ipn->m_i == tn.m_i
++thr-> m_i; // hr->m_i == thr->m_i
++tn. m_i; // n.m_i == tn.m_i
++hr-> m_i; // (*iphr)->m_i == hr->m_i
++n. m_i; // ipn->m_i == n.m_i
}
Output
ctor: R(int) ctor: N(int i)
Tracking references and interior pointers
The following code sample shows that you can convert between tracking references and interior pointers.
// tracking_reference_interior_ptr.cpp
// compile with: /clr
using namespace System;
public ref class R {
public:
R(int i) : m_i(i) {
Console::WriteLine("ctor: R(int)");
}
int m_i;
};
class N {
public:
N(int i) : m_i(i) {
Console::WriteLine("ctor: N(int i)");
}
int m_i;
};
int main() {
R ^hr = gcnew R('r');
N n('n');
R ^%thr = hr;
N %tn = n;
// Declare interior pointers
interior_ptr<R^> iphr = &hr;
interior_ptr<N> ipn = &n;
// Modify data through interior pointer
(*iphr)->m_i = 1; // (*iphr)-> m_i == thr->m_i
ipn->m_i = 4; // ipn->m_i == tn.m_i
++thr->m_i; // hr->m_i == thr->m_i
++tn.m_i; // n.m_i == tn.m_i
++hr->m_i; // (*iphr)->m_i == hr->m_i
++n.m_i; // ipn->m_i == n.m_i
}
Output
ctor: R(int) ctor: N(int i)
Tracking references and value types
This sample shows simple boxing through a tracking reference to a value type:
// tracking_reference_valuetypes_1.cpp// compile with: /clrusing namespace System;int main() { int i = 10; int % j = i; Object ^ o = j; // j is implicitly boxed and assigned to o}
The next sample shows that you can have both tracking references and native references to value types.
// tracking_reference_valuetypes_2.cpp
// compile with: /clr
using namespace System;
int main() {
int i = 10;
int & j = i;
int % k = j;
i++; // 11
j++; // 12
k++; // 13
Console::WriteLine(i);
Console::WriteLine(j);
Console::WriteLine(k);
}
Output
13 13 13
The following sample shows that you can use tracking references together with value types and native types.
// tracking_reference_valuetypes_3.cpp
// compile with: /clr
value struct G {
int i;
};
struct H {
int i;
};
int main() {
G g;
G % v = g;
v.i = 4;
System::Console::WriteLine(v.i);
System::Console::WriteLine(g.i);
H h;
H % w = h;
w.i = 5;
System::Console::WriteLine(w.i);
System::Console::WriteLine(h.i);
}
Output
4 4 5 5
This sample shows that you can bind a tracking reference to a value type on the garbage-collected heap:
// tracking_reference_valuetypes_4.cpp
// compile with: /clr
using namespace System;
value struct V {
int i;
};
void Test(V^ hV) { // hv boxes another copy of original V on GC heap
Console::WriteLine("Boxed new copy V: {0}", hV->i);
}
int main() {
V v; // V on the stack
v.i = 1;
V ^hV1 = v; // v is boxed and assigned to hV1
v.i = 2;
V % trV = *hV1; // trV is bound to boxed v, the v on the gc heap.
Console::WriteLine("Original V: {0}, Tracking reference to boxed V: {1}", v.i, trV.i);
V ^hV2 = trV; // hv2 boxes another copy of boxed v on the GC heap
hV2->i = 3;
Console::WriteLine("Tracking reference to boxed V: {0}", hV2->i);
Test(trV);
v.i = 4;
V ^% trhV = hV1; // creates tracking reference to boxed type handle
Console::WriteLine("Original V: {0}, Reference to handle of originally boxed V: {1}", v.i, trhV->i);
}
Output
Original V: 2, Tracking reference to boxed V: 1 Tracking reference to boxed V: 3 Boxed new copy V: 1 Original V: 4, Reference to handle of originally boxed V: 1
Template functions that take native, value, or reference parameters
By using a tracking reference in the signature of a template function, you ensure that the function can be called by a parameter whose type is native, CLR value, or CLR reference.
// tracking_reference_template.cpp
// compile with: /clr
using namespace System;
class Temp {
public:
// template functions
template<typename T>
static int f1(T% tt) { // works for object in any location
Console::WriteLine("T %");
return 0;
}
template<typename T>
static int f2(T& rt) { // won't work for object on the gc heap
Console::WriteLine("T &");
return 1;
}
};
// Class Defintions
ref struct R {
int i;
};
int main() {
R ^hr = gcnew R;
int i = 1;
Temp::f1(i); // ok
Temp::f1(hr->i); // ok
Temp::f2(i); // ok
// error can't track object on gc heap with a native reference
// Temp::f2(hr->i);
}
Output
T % T % T &