Sunday, August 21, 2005

(Warning: this post is going to be highly tech oriented) I have been giving some thought to the place of C/C++ languages in the era of Rapid Application Development (RAD) languages like Java/C#/Perl/PHP/etc. There is a definite trend in computer application development towards a programming language that is less restrictive on the progammer and more linguistically relevant for the programmer. On this score, languages like Perl take this to an extreme (albeit a good extreme, I think). I think that there are lessons to be learned from these languages that can be integrated into C/C++ without losing its main value as a systems programming language. With that in mind, I am going to list a few things that I would change about C/C++ and at the end how this could be done with tools available today.

1. I am going to start off with my main pet peeve. I am going to show you a bit of code from C/C++, which illustrates my problem: #include <iostream> int main(void) { cout << "Hello World\n" << endl; return 0; } If you passed this code into a modern C/C++ compiler, it would come back with an error. The problem is that this is how to write "Hello World" on the screen from when I learned C/C++. Today you must write: #include <iostream> using namespace std; int main(void) { cout << "Hello World\n" << endl; return 0; } Now the #include statement imports code from the iostream header file into your file and makes available the code from the iostream to the programmer. Having to say both #include <iostream> and using namespace std; to make the stuff from iostream available is redundant. Basically, I am forced to say the same thing twice.

In the Java/C#/Perl world, you only have to say it once with one keyword and where the code you want to use lives. This should be imported to C/C++ (yes, importing from the dark side) as the using statement. This is already part of the language and is familiar to many younger users of C/C++ and it makes clear what is going on in the code.

2. Header files. These things are an abomination. I know that they are there so that when you use a set of functions, the compiler will know that the functions exist and how to find them in the binary representation of the functions when the code is compiled against the binary of the set of functions. This also allows companies who write libraries of functions to keep the implementation away from someone whom licensed the library (this is not so much of a problem these days with the GPL/LGPL).

I think the main problem is that again you have to say things twice. For example, for a function like abs (absolute) you have to have something like this: //abs.h the header file for this function int abs(int num); and //abs.cpp this is where the implementation of abs lives! Oh The Fun! #include "abs.h" int abs(int num) { //blah blah blah }

At the code management level, you have to have a directory where the implementation code lives and a directory where the header file lives and you have to tell the compiler where that directory is before you even try to compile. This is a pain in the ass.

So, in the fine tradition of Perl, we steal the idea from Java/C#/Perl/etc. of having a key word that tells the programmer where the code lives and takes way the need for prototypes of functions or classes. I would nominate the key word of namespace with the name of the namespace to be the directory structure of where the file lives. Again, this keeps a key word familiar from C/C++ and just redefines it.

3. Classes and Namespaces. This is a follow-on from the last section. Classes are treated the same was as functions were above. You have a definition of the class and then the implementation in another file. Something like this: class A { public: int func(void); private: int someNum; }; and #include "whateverTheHeckYouDecidedToCallTheHeaderFileAndWhereItLives.h" int A::func(void) { return someNum; }

As you can see, this calls out for factoring out having to say things twice. I would steal again from the Java/C#/Perl playbook. Make the class all in one place with both implementation and prototype. I would also take a bit more from Java's playbook and require one class per namespace and the added rule of only one namespace per file. So the two files above become: namespace whereILive; class A { public: int func(void) { return someNum; } private: int someNum; };

4. Memory management. I am of two minds on this subject but I will put out some ideas floating around the internet on this. Every time someone talks about improving C/C++, they start talking about adding a Garbage Collector and taking memory management away from the programmer. The only problem that I have with this is that most often the Garbage Collector is a DLL or shared library which must be installed on the users computer before a program can run. This is traditionally done with the installation of the virtual machine for Java/C#/Perl. On this score, I dislike the idea of having to install something on the end user's machine. It is a pain in the ass and can be confusing for the user. I would just have the Garbage Collector compiled into the program. This does have the bad effect of making a small program very bulky but this is offset by the management nightmare of installing and uninstalling versions of the Garbage Collector or any other part of the infrastructure of a language, which could break programs written to the earlier version of the Garbage Collector with the added bonus that the implementer of the enhancements would not have to worry about backwards compatibility with older versions of the Garbage Collector.

I have to be honest here. I was not too keen on adding a Garbage Collector to the language but I was keen on making all pointers/dynamically allocated memory auto_ptr's. This is because having to figure out who owns a pointer to an object is a pain in the ass. Not only that but auto_ptr's are already a part of the standard library for C/C++ and there would be no writing new code involved. Although, I can be persuaded to make a Garbage Collector (and my friend had a great idea of making the Garbage Collector optional if you mark a class (or function?) as doing its own memory management).

5. Pre-processor. #define MUST DIE. DIE DIE DIE! No Macro-Magic! Nada. Zero. Zip!

6. Inheritance. My friend's beef with inheritance is not the idea itself. It is with the way it is conveyed to the programmer and I think he is right and brilliant because I had no thought of it. It is done with one symbol. The mighty mighty ':'. This is just NOT clear at all. It also looks like garbage. So I would replace the ':' with the stolen keyword from Java of 'extends' and the type of inheritance (usually public, protected, private) as the adverbs publicly, protectedly, privately. So a class C which inherits from classes A and B would look like this: namespace C; using A; using B; class C extends publicly A, publicly B { //code goes here }

This makes it very clear to the programmer what is going on and what type of inheritance is happening. I think it is much better than ':'.

7. Libraries. Java/C#/Perl/etc. all have a huge list of objects and functions that are available right out of the box. The STL is great and the Boost set of C++ libraries is getting there. What I would do is make STL + Boost automatically available for programmers (or just a "use" statement away). For things like threading and windowing systems (like programming for Microsoft Windows), I am not sure. I would love to make threading and windowing seamlessly available but at the same time I do not want to write new code.

So how would I do this and make it seamlessly available to older C/C++ code? Well, that is pretty easy. I would write a filter in Perl to translate the new changes to C/C++ into C/C++ code and compile with all the appropriate stuff. The great thing about this is that you maintain the speed of C/C++ while at the same time updating the syntax of the language with the great added bonus of having all those old C/C++ libraries and programs available to be used by the new 'pseudo-language'. You can also make libraries from the language available to C/C++ by just compiling in the normal way and telling the filter to preserve the created header files so it can be used in a regular C/C++ compiler.

Anyway, I think I have babbled on enough. I hope you can get the picture of what I am trying to do across to you. I am going to start implementing these changes on my linux box because GCC is better used on the command line than Visual C++. I will let you know about my progress at some point in the future.

posted by Chris  #6:58 PM | 0 comments |