- IBM’s Pervasive Computing Lab is an intelligent home with most electronic gadgets networked together. All but two gadgets run Java applications on Linux. IBM chose Java because “it’s a good way to insulate developers from the idiosyncracies of specific operating system deployments.”
- Luxor is a XUL toolkit with “a (sic) ultra-light weight, multi-threaded web server, a portal engine and Apache Velocity as its template engine.” XUL is an XML vocabulary for assembling GUI.
The examples cited above unsettled me as they came. A significant portion of the literature on embedded systems and RTOS is devoted to making the reader understand the constraints under which embedded systems operate, viz. low computation power and available memory. Computation power in embedded systems comes at a cost premium and in gadgets having high volume sales, every cent of the price matters. Here we have IBM using Java for their pilot embedded systems to insulate the developers from OS specifics. Note that all but two systems run Linux. You need not read voluminous books to realize that one of the goals of an interactive system is to respond to user inputs as fast as possible. It is interesting to know that people are developing XUL toolkits in Java. Seems like we’ve had too much of it for our own good.
Introduction
During the past few years I have come across several criticisms against C++, mostly from the Java programming camp. Often the rants are just hyperboles coming from newbie programmers who find C++ to be a tough nut to crack. However, what really concerns me is the false impressions created by the Java marketing brigade against its single largest competitor and the second most widely used systems programming language after C.
The DotCom era saw the spawning of large armies of Java programmers with a three month gestation period. As of now, the number of educators for Java and the people educated in it is very high. Java was on my study plan, and I did start learning it but the mediocrity of the language, its disrespect for the programmer’s skill and the spin surrounding it put me off it.
This article was concieved as a rant against Java but it has evolved to cover other programming lanugages and lays greater emphasis on the strengths of C++ than on weaknesses of Java.
C++ is Flexible
One of the strengths of C++ is its flexibility. It allows you to go down to the level of C and direct memory addressing and up to the level of expressing design1 and algorithms in a generic form. One rant against C++ is that it does not have any internal consistency or coherent design. I don’t fully understand that statement but it possibly refers to illogical mixing of features that cause potential problems. Successful C++ programming, therefore involves a lot of idioms and there are excellent books2, 3, 4 that can concisely explain these idioms. Fortunately, these idioms are quite intuitive and using them in practice soon propagates to the level of subconscious.
The strength of C++ shows in its multi-paradigm programming capabilities. More specifically, you can do Imperative, Object Oriented, Functional, Logic, and Generic Programming in C++. Most people are familiar with imperative (C-like) and object oriented programming techniques but there’s a whole world outside these that can sometimes be leveraged to write just the aha! pieces of code that cut right through a sticky problem, resulting in more maintainable or faster or comprehensible or reusable code, depending on the case.
Generic Programming (programming with types as parameters) is a powerful new way of coding algorithms and designs in a non-type-specific manner and reusing them for specific types. C++ templates – the generic programming component – are an entire programming language in disguise. They can take integral and boolean compile time constants as parameters along with types, and perform computations on those. The best place to see these working is the C++ Standard Template Library (STL). Though Java (1.5 onward) and C# also have generics, and they might tell you lies regarding their comparison with C++ templates, the reality is that C++ templates offer you more flexibility5 and the valuable option of static type checking. You may read Alexandrescu’s Book1 for an idea of the power that C++ templates bring to Object Oriented Programming.
Functional Programming is very different from the imperative style of programming found in, say, C or BASIC. The basic idea of this style is that all computation is by applying functions to some data. As in mathematics, functions themselves can act as data for other functions, that is, functions can be created at run-time and they can be passed around as arguments or return values. Besides, variables and assignments don’t figure in this style of programming. Functional programming style can considerably improve the ease of comprehension and verification of complex control logic. This is the reason why it is the primary programming style for Artificial Intelligence applications.
There are several good C++ libraries that enable usage of Functional Programming primitives in C++ programs. FC++ is one noteworthy library that uses C++ templates to the extremes to allow statically typed functional programming in C++. What is really exciting is that since the programming language is the same, you can apply the right programming style to various parts of your application and they work in unision.
The most important thing with C++ is that it respects the programmer’s intelligence and doesn’t try to act as a nanny with a whip. C++, unlike C, makes it more difficult to breach certain constraints like type safety. However, unlike Java, it provides means to breach those constraints. It’s just that they have to be explicit, like saying reinterpret_cast<Foo>
instead of (Foo)
thereby making it clear to anyone that the compiler doesn’t know zilch about converting the argument to the target type and it is the programmer who knows that they are binary compatible.
C++ Goodies
Comparing C# or Java and C++, there is a cross-over point of complexity beyond which C++ language features go a long way in allowing one to write aha! pieces of code that are easy to comprehend and maintain. That point is reached rather soon in an average industrial project and at that point all that the programmers can do is deal with the explosion of interfaces, classes and methods that are required to get around the lack of some very basic programming facilities. It may be easier to get started with Java but no easier to get things finished with it.
Enumerations: Enumerations in C++ can be used to limit the set of integral values that a variable of the enumerated type can use. The compiler will ensure that you don’t ever assign a value to the enumerated type that is not valid. Another nice thing about enumerations is that they are compile time constants. What can this buy you? I’ll illustrate with just one example – compile time look-up tables. In one implementation of the State Pattern, I had to create a collection of the instances of each concrete state. The states had names like Prolog
, Content
etc. and I used a function like getState("Prolog")
to fetch them from a look-up table. However, if I misspelt a state name, the error would be caught only at run-time. Secondly, there is a run-time overhead for look-up. The solution was to create an enumeration of concrete state classes and store their instances in a vector. Indexing on the vector could be done through the enumeration symbols like state_box[Prolog]
. Safer, faster. Enumerations have made it to Java 1.5. (Aside: The scope of an enumerated symbol in C++ is the scope in which the enumerated type was declared. C# does it better by making the enumerated type a scope in itself. There is a proposal to introduce this in C++ as well.)
Operator Overloading: This is one feature that exists solely for the pupose of making the programmers’ task easier. Yes, operator overloading involves excercising care to maintain consistency with the basic usage of those operators, but it frees up the client with a lot of bother. Two things that make operators preferable over functions are that you need not remember the number of parameters and you don’t need to know their order. Besides the usual arithmetic operators that may mostly be used for mathematical and scientific applications, two operators that are very useful for business applications are typecast operator and function call operator.
The type cast operator allows you to control the exact behaviour of a cast from one type of object to another unrelated type, especially when you can’t modify the target type definition. This frees up the necessity to bunch types together into some kind of inheritance heirarchy just for the sake of type inter-conversion. It is also syntactically more explicit and concise than writing prototype builders. Aside: In C++, if you have a constructor for type B that takes a single parameter of type A, you can use type A objects wherever you expect type B objects. This is mostly very convenient, but sometimes leads to unexpected results. If you want to control inter-type conversions, you may declare all single-parameter constructors as explicit
so that the compiler won’t perform automatic conversion.
The function call operator (operator ())
is used extensively in the STL to encapsulate function objects – classes whose instances encapsulate a function. The function call operator is much more readable at the place where it is used rather than methods like swap.execute()
, where execute can be any arbitrary function.
Templates: The compile time nature of C++ Templates and their capability of performing arithmetic on compile time constants and type inference provides a great deal of power to the programmer without run-time overheads (though there is some space overhead that sometimes becomes critical in embedded applications). C++ Templates are Turing Complete, which means that they are a programming language in disguise. They act like code-generation engines while Java and C# generics are like compile-time hacks for automating certain tasks and they don’t even come close to the functionality afforded by C++ Templates.5, 6, 7 To compare C++ Templates with C# generics, the following are the major differences:
- C# does not allow non-type template parameters: In C++ you can write something like
template <int i> foo(){}
. Inside the template, you can even perform computation on the parameter. Alexandrescu1 has shown how to use this feature to build compile time collections of types that can be used with other template algorithms for tasks like sorting types beloging to the same inheritance heirarchy in the order of generalisation, generating class heirarchies automatically and other nifty applications. - C# does not allow explicit specialization: That is, you may write a template class or function with some generic behaviour for any type
T
but provide a separate behaviour for some specifc typeFoo
. This allows, for example, writing a generic comparator for any typeT
, that simply uses operator<
but specialise it forchar *
to usestrcmp
standard function, instead of the operator that would otherwise have compared pointer addresses. It can also be used to specialise for better performance. As an example, there may be a generic container implementation for any typeT
and a special implementation forbool
that would be geared toward better space-time efficiency. - C# does not allow partial specialization: C++ Templates allow for a custom implementation for a subset of the type arguments. This feature is used extensively in FC++ for currying.
- C# does not allow the type parameter to be used as the base class for the generic type, and
- C# does not allow type parameters to have default types: This closes the door on a whole aspect of programming called policy based implementation. The idea of policy based classes is that they implement only the mechanism for some functionality while policy decisions are fed into them via policy classes. The usage of policy classes allows users of the template class to customise it without bounds for application specific policies. The usage of default types allows the library writer to provide a combination of the most commonly used policies by default. So, for example, you usually create a
std::vector<Foo>
to have a linear collection ofFoo
but ifFoo
objects have to be allocated through some custom memory pool, you can pass the allocator tostd::vector
likestd::vector<Foo, myAllocator>
since it takes a second parameter for allocation that defaults to an allocator usingstd::new
. - In C#, a generic type parameter cannot itself be a generic, although constructed types may be used as generics: C++ allows template parameters themselves to be templates that can be instantiated in the body of the template that receives them with other types. For example,
std::stack
is an adaptor template that provides the stack operations using another linear container as the storage. If template template parameters were not allowed, the user would have to declare something likestd::stack<T, vector<T> >
instead ofstd::stack<T, vector>
. The specification ofT
in the first case is redundant since a stack ofT
would only use a container ofT
for storage.
STL: The standard C++ library includes a set of generic concepts, containers, functions and algorithms that are collectively referred to as STL (Standard Template Library). The use of STL in applications can significantly cut down the development time, code complexity and size in LoC and run-time errors. I can’t discuss how this comes to be in the scope of this article but you are invited to experience them yourself.
Just to illustrate, how do you perform binary search on a sorted linear collection lc
? Like this: std::binary_search(lc.begin(), lc.end(), value)
where the first two parameters specify the range in the collection and the third parameter is the value to be searched. This algorithm uses operator <
for comparison but you can pass your own comparator as a fourth parameter if required. Now, how do you arrive at a sorted collection? That’s even simpler: std::sort(lc.begin(), lc.end())
. By the way, a simple C-style array is also a linear collection and you can use indexing to specify the range. Another interesting illustration is automatic iteration. In Java 1.5 you have: for (Foo i : lc) {/* Do stuff; */}
. Note that in this construct you have to specify the type of the elements stored in the collection and this construct will necessarily iterate through the entire collection. Compare this to std::for_each
: std::for_each(lc.begin(), lc.begin() + n, do_stuff)
. The first difference is that std::for_each
works over a range so it can be applied to a portion of the collection. Secondly, you don’t have to specify any types because the algorithm can infer the type of the elements from the type of the collection. This is not possible in Java because internally all generic containters are containers of objects.
Automatic Storage: Java has a GC because all memory allocation and deallocation happens at run-time. C++ has automatic storage for local variables which means that the code for allocating and deallocating the memory for the object is generated by the compiler without the programmer having to bother about it. I have taught Java programmers C++ and most of them still end up newing objects where they could have declared them as automatic. Automatic variables along with std::auto_ptr
smart pointer can remove the burden of memory management, except in cases where the owner of the dynamically created object is not the same as the creator and this constraint itself is case dependent.
In case you don’t believe me, you are free to examine the source code of my project, Xqueeze. Release 0.3 has about 1800 lines of code and only three delete statements. All but one of these appear in the destructors of classes using the pImpl idiom, which means that the entire library could have been written using a single delete statement!
Variables with Value Semantics: In Java, all user-defined data types can only be accessed through references. While this may be quite adequate for business applications, it robs the programmer of the benefits of functional style of programming. Variables with value semantics offer two major benefits: automatic storage (see above) and non-modifiability. If you pass an object by value to a function, you can be assured that the function only gets the value (actually a copy) of the object and it can perform operations only on its own copy. This can do a great deal to improve the verifiability and debugging of a function.
const references: Manish Jethani has said enough about it5, so I need not copy it all here :) This feature, to some extent, allows for attaching value semantics to a function parameter while alleviating the overhead of creation and destruction of a temporary, which may be significant for user-defined classes. The difference is that inside the function, you can not invoke non-const methods of the object passed, as they could modify the actual object.
Destructors: C++ defines precise rules for when an object goes out of scope and a special function called the Destructor is executed at that time. This has fostered a whole idiom called RAII that stands for Resource Aquisition is Initialisation and it’s complementary implication that destruction is resource release. The details can be found in the preceding link, but it suffices to say that RAII frees up the user of a class based on this idiom from calling initialise()
-cleanup()
function pairs. Besides, RAII can cover any resource while GC only covers memory.
Multiple Inheritance: Though I’ve never used multiple inheritance in my own code, according to Grady Booch, “Multiple inheritance is like a parachute; you don’t need it very often, but when you do it is essential.” I hope I don’t have to introduce Grady Booch!
Static Typing and Computability: C++ is not only fast to execute, it is also quicker to debug. Dynamically typed languages like Python help a lot in prototyping where the interfaces etc. change very frequently. However, once the implementation design is arrived at, static typing goes a long way in detecting errors at compile time. Why do something manually when it can be automated? Post compilation debugging required for C++ programs is minimal. Of course, you have to have a verifiable design which you code against and you must use STL as far as possible.
Compared to this, most of the dynamically typed languages are interpreted and it is not necessary that every branch of execution goes through every piece of code. If you have made a mistake in some exception handling code, you can not discover it unless the exception it handles is raised. You have to build careful test harnesses. Then again, you never know what you might have missed. At least you are assured that the compiler will go through each and every piece of code you write. The more error-checking you can build into the compiler, the lower the chances of unpredictable run-time errors.
Portability of C++ applications
During Java’s early days, one of its claims to fame was portability and consistency of implementation. C++ had a lot of dialects, depending on the vendor and there were too many “undefined behaviour” bits. In 1998, the ISO/ANSI C++ standard was published and six years after its publication, compilers with excellent standards compliance are here. GCC 3.x, MSVC++ 7, Comeau C++ are some of the highly compliant compilers I know of. By the way, Microsoft is spending a lot to continue improving C++ support on Microsoft Windows. The fact that Microsoft has developed C++ bindings for the .Net CLI and the CLI is available on many platforms, gives you the option of portability via the bytecode way.
Another of Java’s claims to fame was the write once, run anywhere capability. While I have come across portability issues in Java across compilers and JVMs, the JVM based model did improve the situation. However, as with Java, having a VM is not as important as using portable libraries. To this day, C and C++ programmers write a lot of applications that interact directly with the OS level system calls. Applications8 that aimed to be cross-platform from the very beginning developed an OS abstraction library as the first thing. Today we have highly capable and widely ported wrapper libraries for OS abstraction, GUI toolkits etc. available off the shelf as Free Software. The nice part about having an abstraction layer instead of a VM is that a lot of it could be simple remapping of types and symbols that gets done at compile time with very little run-time overhead.
A nice feature of C++ is static linkage. If you are not sure that your clients would have all the libraries that you used to develop your application, you can link your application with them statically. This would afford wider distributability at the cost of larger program size. With Java, this is not an option. A not so nice part about C++ is the Application Binary Interface (ABI): the specification regarding the layout of object code in executables and name mangling rules. This creates some problems for (and due to) vendors of binary only applications.
Opinions
Most of what follows is opinion, not verifiable facts. If you don’t have time to kill, you may stop reading here.
User time is more important than programmer time: There’s a saying among the developer community that goes, “Programmer time is more important than CPU time.” Indeed, the battle between lazy programmers (it is a programmer’s virtue to be lazy) and automation of mechanical tasks is an ongoing one. However, most programmers develop software for users, which may be programmers themselves, and the merit of a tool for winning this battle also lies in how little it affects the performance and usability of the product the programmer is developing.
If a programmer develops software for an end-user, if that software happens to be used by multiple end-users and if the users use that software multiple times a day, is it prudent to carry the “please use better hardware” attitude to save some dollars of programmer salary? It may make business sense, but my opinion is that its plain wrong. User time is more important than programmer time. As a user, I get annoyed with the time it takes to initialise Java based applications. Besides, Java programs have a memory footprint that I can’t afford hardware for.
Better hardware can be put to better use: While the hardware manufacturers are able to increase the computing power of machines at a fast pace, there seems to be no limit to the situations where deployment of a computer would improve things. However, a lot depends on how cost-effective such a deployment is. Take the case of education. Often, distributing course content in digital format is much cheaper than the printed format. However, it requires that every student have acces to a computer, which may not be affordable to all. The institute needs to provide computing facilities. Many institutes realise this but can not go ahead because of the cost of entry barrier. Your clients may be able to afford your software (it may even be Free Software) but what about the affordability of hardware required for your software?
These days, software services delivered over the network is an emerging trend and I am of the opinion that it holds promise. The software developed for these services is a single implementation that delivers to everyone across the world. The platform of deployment is very well under control. The developers generally are large corporations with loads of money to buy really fast and really expensive hardware so they don’t worry about the performance. Does the user of those services not care either? I would guess it’s otherwise. Most of the category leaders in such services pay a lot of attention to the performance aspect. Take Google, for instance, or or Amazon. If you can afford better hardware, use it to provide better service.
A programmer who knows… Knowing the syntax of a language by heart is not enough to make a good programmer. Knowing each and every method of each and every class of a library is not required to make a good programmer (yet I have heard so many interviewers ask people about methods of classes in a class library, it seems pointless). Every programming platform has a set of idioms on which most good program chunks are built. Patterns is another often used term for such practices. A programmer has to understand these idioms and patterns to be able to write good code. IT managers must remember that while they can hire an army of mediocre programmers for less, a programmer who knows can make a lot of difference. Then again, there are some applications that just need to be developed as soon as possible. There’s a term for those in developer jargon: quick and dirty. In a skilled programmer’s hands, C++ can be very quick to code but it doesn’t forgive the dirty.
-- Tahir Hashmi. 2004-02-22
Changed on 2004-03-12 to include “C++ Goodies” section and separate opinions from technical issues.
References
Modern C++ Design, Andrei Alexandrescu, AWP 2001. ↩︎ ↩︎ ↩︎
Effective C++, Scott Meyers, AWP 1998. ↩︎
More Effective C++, Scott Meyers, AWP 1996. ↩︎
Effective STL , Scott Meyers, AWP 1998. ↩︎
Weblog discussion on Heljsberg’s interview by Bruce Eckel on C# generics, with regards to type safety. ↩︎ ↩︎ ↩︎
Differences between C++ Templates and C# generics (from MSDN). ↩︎
Mozilla/Netscape, Subversion, Apache Web Browser are applications that I remember seeing use an OS abstraction layer. ↩︎