Quote:Original post by guardian24 Where is the ugliness, comparing to C#? |
It's in your hideous code formatting [lol]
For the most part, the language itself does not *look* bad. Neither does C++, for that matter, unless you're playing with the more convoluted depths of it. It's the sheer complexity of things, and the mass of finicky details that really complicate things.
For example, consider destruction.
ref class Foo
{
public:
~Foo() { Foo::!Foo(); }
!Foo();
};
So we've got a destructor, which more or less behaves like a destructor. And then we've got this extra thing tacked on, which is the finalizer. The destructor implementation, in turn, explicitly calls the finalizer. These get remapped into the disposable pattern. Foo implements IDisposable, and two methods are generated, Dispose() and Dispose( bool disposing ). Finalize() is also generated. Those methods call the destructor and finalizer as they see fit. GC.SuppressFinalize is magically called for you at some point as well. The compiler emits more calls to these functions around your code as needed. You now use
delete obj; instead of obj->Dispose(), and destructors are also chained automatically per C++ rules -- which of course means that you need to mark your base class destructors virtual like in C++. Oh, and you're not allowed to explicitly implement IDisposable. In other words, this is illegal:
public ref class Foo : IDisposable
{
public:
virtual void Dispose()
{
}
};
Another example. Every variable can now be a value, a pointer, a reference, a handle (managed reference) or a tracking reference(ref/out in C#) to one of those things. You can form a reference or a pointer to any of the first four, but the reference will show up as a pointer when exported with an attribute, and who knows how other languages consume that. You can form a handle to any ref class, but nothing else. And boxing gets involved somewhere in there, allowing you to have boxed handles to value types. Don't forget NULL for pointers but nullptr for handles. You can pass anything as a tracking reference, but you have to use the [Out] attribute to differentiate between what C# calls 'ref' and 'out'. I also noticed something about some "take-address" operator, but really just didn't care. All that only applies to managed classes; native classes and structs can't be exposed as values. On top of that, you can take pointers to things inside managed objects, but only by using
pin_ptr and
interior_ptr, which are pseudo-template pointer types that the compiler inherently knows about. Then you've also got the pseudo-template array type. So some of the syntax here:
array<Byte>^ data = gcnew array<Byte>(100); //gotta use gcnew. Can't use ^ on the rhs.
pin_ptr<const unsigned char> pinnedData = &data[0]; //automatically unpins via RAII. And make sure you put const in the right place!
array<array<MyClass^>^>^ local; //whoo jagged arrayFor more fun, look into any of the following:
* String literal handling (when is it a char*, and when is it a String^?)
* Casting behavior, safe_cast, and what the compiler does with C style casts
* Context sensitive keywords -- I can happily name a variable 'delegate' if I want!
* Explicit overriding
* The rules for mixing generics and templates, and metadata for managed templates
* The myriad of rules for what you can and can't do with tracking references