I use one of the following two containers when friendly interop with garbage collection is preferred:
template<typename T> ref class GcPlainPtr sealed {
T* ptr;
public:
GcPlainPtr(T*ptr): ptr(ptr) { GC::AddMemoryPressure(sizeof(T)); }
!GcPlainPtr() {
GC::RemoveMemoryPressure(sizeof(T));
delete ptr; ptr = nullptr;
}
~GcPlainPtr() { this->!GcPlainPtr(); } //mostly just to avoid C4461
T* get() {return ptr;}
static T* operator->(GcPlainPtr<T>% gcPtr) { return gcPtr.ptr;}
static operator T*(GcPlainPtr<T>% gcPtr) { return gcPtr.ptr; }
};
The previous container looks sufficient for your needs. You can use it as follows:
ref class MyManagedClass {
GcPlainPtr<userData> myUserData;
MyManagedClass(...bla...)
: myUserData(new userData(...))
, ...
{...}
AnotherMethod() {
std::cout << myUserData->data1 << '\n';
AddItem(1, myUserData.get());
}
}
The advantage of the previous approach is that even if you forget to dispose of the objects, the memory pressure is reasonably updated so that garbage collection occurs at the appropriate frequency.
If you know the size of the data element you're allocating roughtly, but it's not merely the direct size (i.e. the native struct or class allocates memory internally), the following variant may be more appropriate:
template<typename T> ref class GcAutoPtr sealed {
T* ptr;
size_t size;
public:
GcAutoPtr(T*ptr,size_t size) : ptr(ptr), size(size) {
GC::AddMemoryPressure(size);
}
!GcAutoPtr() {
GC::RemoveMemoryPressure(size);
size=0;
delete ptr;
ptr = nullptr;
}
~GcAutoPtr() { this->!GcAutoPtr();} //mostly just to avoid C4461
T* get() {return ptr;}
static T* operator->(GcAutoPtr<T>% gcPtr) { return gcPtr.ptr;}
static operator T*(GcAutoPtr<T>% gcPtr) { return gcPtr.ptr; }
};