C++/CX refers to Windows Runtime strings as Platform::
with a hat instead of a star. But even though the string wears a hat, it is not an Object^
.
The String^
type is a representation of the Windows Runtime HSTRING
. And of the rules of HSTRING
is that a null pointer is a valid HSTRING
, and it represents the empty string, that is, a string with no characters.
This makes String^
a strange sort of beast.
String^ s = L""; // sets s = nullptr
If you assign an empty string to it, you get nullptr
back.
void f(String ^s) { if (s) { /* string is not empty */ } }
Testing a String^
against nullptr
tests whether the string is empty.
String ^s = nullptr; // represents empty string auto data = s->Data(); // legal! returns pointer to L"" auto length = s->Length(); // legal! returns 0. auto equal = s->Equals(L"nope"); // legal! returns false.
That’s right: I dereferenced a null pointer and it felt good.
Calling methods on a null String^
pointer is legal, and the operations are performed on an empty string.
This weird behavior of null String^
pointers has consequences beyond just strings. If you convert a null String^
to Object^
(a boxing operation), the null-ness is preserved:
String^ s = L""; // s is nullptr Object^ o = s; // o is nullptr!
This differs from the behavior in other projections like C#, JavaScript, and C++/WinRT, where boxing an empty string produces a non-null object (that in turn holds an empty string).
The fact that a String^
is not an Object^
means that you cannot reinterpret between them.
String^ s = /* some value */; Object^ o = reinterpret_cast<Object^>(s); // crash
The reinterpret_cast
will treat a String^
as an Object^
. But a String^
is secretly a HSTRING
, whereas an Object^
is secretly an IInspectable*
. The reinterpret-cast tells the compiler to treat this HSTRING
as if it were an IInspectable*
, and bad things happen, since the compiler is going to try to call the AddRef
method from the IInspectable
‘s vtable, but HSTRING
s don’t have a vtable, much less a vtable with AddRef
in slot 1.
What you need to do is box the string into an object and unbox the object back into a string.
Object^o = s; // box the string into an object String^s = static_cast<String^>(o); // unbox the object into a string
Bonus chatter: C++/CX delegates are also not objects, even though they too wear hats.
C++ compilers oddly tend to be ok calling non virtual methods on null objects even if that's headed into undefined behaviour territory. Better hope the method checks this for null.Sounds like the projection is probably doing tnis with the methods for String
Imo empty string should be different to null strings as a null thing is not a thing while an empty string is still a string. Most of the time this tends to be the...
So this is an interesting one, C++11 and beyond forbid exactly that calling an instance method on a null pointer is explicitly undefined behavior. Unfortunately some code bases rely on that behavior. I can't recall the specific code bases but I know that the Linux Kernel has run into a version of this as well to the point they now compile with explicitly. I know some of the libraries used by Chromium (and...
In some languages (like C++), strings are value types. There is no “null” value for
std::string
. Even in languages where strings are reference types, distinguishing an empty string from a null string can be cumbersome (such as in JavaScript and Perl), and coding conventions generally consider empty and null strings as equivalent.Even in a language like C# tries to skirt the line between reference and value. It has value equality and assignment semantics, immutability, compile-time const-ness and interning... but is always showing it's true reference self whenever null shows up.
I've often wished the C# language designers had gone all-in and made string a value object alongside int. Maybe it would be clunky, but using a special value to represent a missing string...
> That’s right: I dereferenced a null pointer and it felt good.
Oh bravo!