lördag 8 mars 2008

C++ vs. C#

For you who think that C# is the master language, why really is it? C++ has been around for soon 30 years, and is still considered to be the one language for many developers. You might say that those developers are a bit out of date or conservative, only sticking to the one language they learned several years ago, but the thing is I really think C++ has some interesting stuff not supported by other languages such as C#.

Memory Control
For one think, C++ has total memory control. You can allocate memory when every you want to and then use it any way you like. Then, when you don't need the memory you release the memory manually. This means of course that it's easy to shoot your self in the foot and forget to release the allocated memory, but also that you aren't required to have a garbage collector running in background thread taking up important cpu power (if the cpu power is important). Also, garbage collectors can only release an instance when there are exactly no references left to it left, so if there are circular references then those can never be released (except when exiting the application).

The garbage collector can thus not remove something on demand. For example, at some point in your program you might know for sure that "this instance is not going to be used any more, if it is then there is a bug", but you don't know for sure that there are no references left to it (perhaps you put it in a list somewhere and forgot to remove it?) and thus the garbage collector might not remove it and your memory consumption will grow larger and larger. After running your program for some time you will notice that the memory usage becomes large, and because you have no control over memory you cannot simply search in the code for a missing "delete" statement (which you can in C++, or actually search for new with a missing delete). And because the garbage collector can clean up the memory when ever it wants to, you cannot rely on the destructor being called at an appropriate time. C# (or Microsoft) thus doesn't recommend you to use destructors, but rather to implement a method such as void Dispose() which you must call manually to dispose any unmanaged resources being held by that object (you can actually use destructors, but what if you in that destructor adds a reference to the object somewhere? Suddenly the object is alive and shouldn't be destructed...). But if you forget to call Dispose in your code, you could easily lock up resources and get unpredictable behaviors.

In C++ you have better control to whether put something on the heap or the stack. If on the stack, you know that the destructor will be automatically called when leaving the scope, something not possible to do using C#. In C# you instead need to implement the Dispose method, and then create an instance of that class in a using-block, which will automatically call Dispose when the instance leaves the scope. There is no other way to do this in C#. If you for example have a lock helper class that automatically locks a given object when constructed and releases the object when being destructed, you could write code like this in C++:
void doDangerousStuff(Object &a, Object &b)
{
LockScope lockScopeA(a);
LockScope lockScopeB(b);

// Do dangerous stuff on a and b
}
In C# you could write this:
void doDangerousStuff(Object &a, Object &b)
{
LockScope lockScopeA(a);
LockScope lockScopeB(b);

// Do dangerous stuff on a and b

lockScopeA.Dispose()
lockScopeB.Dispose()
}
But what if you got an exception in the middle of the code? Then the Dispose() method would not be called, so instead you should use the using keyword:
void doDangerousStuff(Object &a, Object &b)
{
using( LockScope lockScopeA(a) )
{
using( LockScope lockScopeB(b) )
{

// Do dangerous stuff on a and b

}
}
}
But this doesn't look good, and what if you need to add more locks on other variables, the code would just grow more and more to the right, and at least to my opinion that's something you should avoid to the max. C# thus doesn't give you a good solution to this problem.

Actually, in C#, you can control whether to put something on the heap or on the stack. Classes are automatically put on the heap when allocated, and structs are stored on the stack. This means that it theoretical should be possible to call the destructor when the instance of a struct leaves the scope, but unfortunately C# doesn't allow you to add destructors to structs, so no good luck there :(

Headers and Linkage Errors
Another positive thing of C++, which some of you might find confusing, is the possibility to declare a function in a header, but then never implement it. This lets you design a class without actually giving an implementation. If you then try to compile the program you will not get any compiler errors but instead linkage errors. This means that "the code looks good, but I couldn't find the definition of these functions". You cannot do this in C#. Instead you need to add a method with an empty body, and in that body you throw an exception or assert false. Thus, instead of getting linkage errors, you get runtime errors.

The Const Keyword
I just can't figure out why languages such as Java and C# doesn't support the use of a const modifier. In C++ you can state a class method to be const, meaning it can't modify the state of the object. This is very useful if you for example want to pass an object by reference to a function, but want to make sure it isn't modified. You then mark is as const, and the function can only call the methods declared on that object that are marked as const. The state of that object will thus remain unchanged.

In Java and C# this is not possible. In Java you can at most (at least to my experience) send primitive types such as int, byte and float which will be passed as values (copies) and thus any change of these values in the function will only affect the copy. In C# you can do something similar, but even structs will be passed by value, enforcing the state of that object not being changed. This though would require you to rewrite every class you want to be able to pass to a function without it being modified, which of course isn't a very pleasing solution. Quite the opposite actually, as it also enforces you to make copies of the object every time you want to use it in another function.

To get around this (at least what I think) Java and C# have implemented some classes that are immutable. One such class is the String class. If you have ever examined this class you might realize that there is no way to modify it, it is fixed. To change it you actually have to instantiate a new string which holds the changed value. So, functions taking strings as parameters cannot modify them, even though they are passed by reference. But is this the solution you want for your big universal state that you want to pass a function which should not modify it?

Macros
One last thing to mention about C++, and which is also not supported by C#, is macros. A macro can totally destroy your code, but also let you write complicated beautiful code in only a few lines. If you for example have a lots of classes that you want to register to a factory, instead of writing:
factory.RegisterClass("MyClassA", &MyClassA::Constructor);
factory.RegisterClass("MyClassB", &MyClassB::Constructor);
you can define a macro and simply write
#define REGISTER(class) factory.RegisterClass(#class, &class::Constructor)
REGISTER(MyClassA);
REGISTER(MyClassB);
Isn't that cool? You can actually do a lot more complicated stuff if you want to, and even generate default class implementations this way. With macros you can do anything! The closest thing to macros you have in C# is reflection. With reflection you can for example extract the name of a class dynamically, and get a function from a class based on a string (the name of that function), but if that class hasn't implemented that function you will instead get a runtime error. In C++ this error will appear when compiling!

Why C# Still is Cool :)
But to turn to the other side of the street, C# isn't that bad after all. Compiling C++ code will give you a headache if compared to compiling C# code, which will only take a few seconds. Also, running C# code isn't actually that much slower than you might think. In some cases you will actually find out that your code is equally fast in C# as in C++! And C# has a lots of cool features not supported at all by C++ (and many other languages) such as properties, attributes, partial classes, yield and lock statements, coalesce operator ??, just to mention a few. And with C# 3.5 you have support for even more nice stuff, such as lambda expressions, LINQ and extension methods.

Actually, the reason to why I'm not writing a post on why C# is better then C++ is that it's so trivial :) C# have so many things C++ misses out on, but I just felt I had to write this article so that people don't think that C++ is nonsense. Because it isn't! At least in the gaming industry, C++ is still the leading language, with the reason probably being "you have more control" and "it's fast"! And around 2009, the next version of C++ is planned to be shipped, called C++0x, which hopefully will give the language a boost to survive the future!

3 kommentarer:

Peterh sa...

Hello. I think you have missed an quite important part of the properties of the .net/C# GC. It builds a hierachy of objects from a root object. Objects not in this tree (your program) can be disposed of. Therefore, unlike java i believe, circular references is typically not a problem.

johno sa...

crap, I lost my post due to stupid Blogger implementation of account management... :(

const Class& RULES. This lets you express critical design decisions, namely "you can send messages to this object but you can't change it's state".

Header/Linkage stuff (as described) RULES++. This allows you to wear many different "coding hats".

First you implement some method by calling other methods that don't exist. Then you compile. You get compiler errors, so you write the method signatures in the appropriate header. Then you can keep on "designing".

Once you are done "designing" (i.e. you don't have any more compiler errors), you switch to your "implementation" hat and start implementing your design. Often the starts a whole new "design" session, and around you go.

I feel that this is a critical aspect of writing good code. Also, it makes refactoring WAY easier; I often comment out an entire .cpp file and start changing the interfaces, and incrementally bring the code back in the "implementation" phase.

It should be noted that I compile ALL THE TIME; I very rarely fix more than the first compiler error I "F8 to" before compiling again. Now it should be even more clear why I hate codebases that don't compile fast as lightning... (hello all you Boost-fanbois...)

johno sa...

Oh and by the way, well designed C++ code DOES NOT take a long time to compile.