Search code examples
c++-cli

Accepting managed struct in C++/CLI both with "hat" operator and without. What is the difference?


I've got a C++/CLI layer that I've been using successfully for a long time. But I just discovered something that makes me think I need to relearn some stuff.

When my C++/CLI functions receive an instance of any managed class, they use the "hat" operator ('^') and when they receive an instance of a managed struct, they do not. I thought this was how I was supposed to write it.

To illustrate as blandly as I can

using Point = System::Windows::Point;
public ref class CppCliClass
{
    String^ ReturnText(String^ text) { return text; }  // Hat operator for class
    Point   ReturnStruct(Point pt) { return pt; }      // No hat operator for struct
};

I thought this was required. It certainly works. But just today I discovered that CancellationToken is a struct, not a class. My code accepts it with a hat. I thought it was a class when I wrote it. And this code works just fine. My cancellations are honored in the C++/CLI layer.

void DoSomethingWithCancellation(CancellationToken^ token)
{
    // Code that uses the token.  It works just fine
}

So apparently I can choose either method.

But then what is the difference between passing in a struct by value (as I've done with every other struct type I use, like Point) and by reference (as I'm doing with CancellationToken?). Is there a difference?


Solution

  • ^ for reference types and without for value types matches C#, but C++/CLI does give you more flexibility:

    • Reference type without ^ is called "stack semantics" and automatically tries to call IDisposable::Dispose on the object at the end of the variable's lifetime. It's like a C# using block, except more user-friendly. In particular:

      • The syntax can be used whether the type implements IDisposable or not. In C#, you can only write a using block if the type can be proved, at compile time, to implement IDisposable. C++/CLI scoped resource management works fine in generic and polymorphic cases, where some of the objects do and some do not implement IDisposable.

      • The syntax can be used for class members, and automatically implements IDisposable on the containing class. C# using blocks only work on local scopes.

    • Value types used with ^ are boxed, but with the exact type tracked statically. You'll get errors if a boxed value of a different type is passed in.