Search code examples
datetimepointersc++-climemory-addressmanaged

C++ assigning managed pointers to an object with and without the address operator "%"


I am relatively new to C++ and am learning how to use pointers, but I ran into something that has really confused me about assigning them. I have code similar to the following:

DateTime^ dt;

...

while(true)
{
    if(!dt)
    {
        //both of these work - compile and the loop will break after a minute
        //dt = DateTime::Now;
        dt = %DateTime::Now;
    }
    else if ((DateTime::Now - *dt).TotalMilliseconds > 60000)
    {
         break;
    }
}

Why do both of these work? From my understanding the dt = %DateTime::Now sets the dt pointer to the reference of the object returned by DateTime::Now which makes since to me. But what then is happening for in dt = DateTime::Now because if DateTime::Now does return an object then shouldn't it give me a cannot convert DateTime to DateTime^ error? It seems like in order for this second one to work, DateTime::Now would need to return the reference of the DateTime object created by DateTime::Now in which case shouldn't the first one dt = %DateTime::Now give the similar error of cannot convert DateTime^^ to DateTime^ or do smart pointers work differently than normal pointers?


Solution

  • sets the dt pointer to the reference of the object returned by DateTime::Now

    That's where you went wrong, DateTime::Now does not return an object. It returns a value, DateTime is a value type. The distinction between reference types and value types is completely absent in C++ but is a very big deal in .NET. You must understand it to write efficient code, this is very much not efficient.

    DateTime^ dt;

    That's where the train jumped off the track, since DateTime is a value type you should declare this variable without the ^ hat. Sadly the C++/CLI compiler allows this syntax and actually implements it. The variable stores a boxed copy of the value. Intentionally boxing values is something you always want to avoid, the boxing conversion is not cheap. And the entire point of .NET having value types at all was to make programs fast.

    But yes, as you discovered the C++/CLI compiler knows how to box values without you having to be explicit about it. While it should be avoided, it is not unusual at all that values are boxed sometimes. No way to call the virtual ToString() method otherwise for example. So you don't have to use %. Boxing creates the illusion that all value types derive from System::Object.

    if(!dt)

    I have to assume that this is what you were really after, detecting that a variable is "not initialized". There's a much more efficient pattern for that in that .NET, you use the Nullable<> type instead. Like this:

    Nullable<DateTime> dt;
    while (true) {
        if (!dt.HasValue) {
            dt = DateTime::Now;
        }
        else if ((DateTime::Now - dt.Value).TotalMilliseconds > 60000) {
            break;
        }
    }
    

    Which can be improved in many ways, you avoid the halt-and-catch-fire version with:

    System::Threading::Thread::Sleep(60000);