10.28.2009

Why C++ Doesn't Suck

It seems like it's becoming easier and easier these days to find rants from people about why C++ sucks. To see how easy it is, just go to google and type "C++ Sucks" and you'll find tons of blogs, just like this one, albeit with people making the opposite argument as the one I'm going to make. Since there seems to be so many people explaining why C++ sucks, I figured I might as well play devil's advocate and argue why C++ doesn't suck.

First, we might as well establish before we even start that the point I'm arguing for here (that C++ doesn't suck) is part meaningless, and part obvious. What does it even mean to say that a language sucks? Sure, there are things that are easier in some languages or other languages, and there are languages that are prone to user error more often than in other languages, but does that make the entire language suck? I think we should agree that in order for an entire language to suck, there must be no compelling reason to use it for any purpose in any industry. Such a language should excel at nothing, and lag behind at everything.

With that out of the way, I think the most logical place to start is by responding to some common complaints about C++. One of the most well known opponents of C++ is Linus Torvalds. I don't necessarily think that just because Linux is his brainchild that his opinion automatically holds more weight than anyone else's opinion on why C++ sucks, but since he's the most well-known, I'll start there.

Linus: "It's made horrible by the fact that a lot of substandard programmers use it, to the point that it's much easier to generate complete and utter crap with it."
Are we talking about a property of the language here, or a property of the people using it? Haskell is more difficult than C++, but not a lot of substandard programmers use it. So is one to infer that the only reason Haskell is a good language and C++ is a bad language is because Haskell doesn't have substandard programmers?

How does Perl fit into this picture? It's just as easy to generate complete and utter crap with Perl, and the reason for that actually is a property of the language. No doubt a lot of people will just respond by saying that Perl also sucks, but far fewer than will say that C++ sucks.

Linus:C++ leads to really really bad design choices. You invariably start using the "nice" library features of the language like STL and Boost and other total and utter crap, that may "help" you program, but causes:
- infinite amounts of pain when they don't work (and anybody who tells me that STL and especially Boost are stable and portable is just so full of BS that it's not even funny)
- inefficient abstracted programming models where two years down the road you notice that some abstraction wasn't very efficient, but now all your code depends on all the nice object models around it, and you cannot fix it without rewriting your app."

First of all, when he says "You" invariably start using... is this the substandard programmer "you" again? In any case, the source code of STL is always available, and Boost is fully open source and peer reviewed. Furthermore, I hate to say something that may seem like a personal attack (even though it's definitely not) but some of the biggest contributors to Boost are probably quite a bit smarter than Linus. Not to downplay him personally or his contributions to the community, but rather the converse, to demonstrate how great and unbelievable the contributions of the people on Boost have been.

I get the feeling that Linus hasn't used Boost very much. I don't blame him, after all he hates C++ and Boost has a high learning curve, what motivation could he possibly have for trying to learn it? But here, answer me this:
- What do you do when writing cross-platform code in C and you need to make heavy use of the filesystem? Can you make an API in C that handles this easily and is easy to use? Even most higher level languages don't have very detailed and complete filesystem APIs that are generic enough to be used across multiple platforms.
- What if you're writing an application built around an i/o completion port model of asynchronous io and the app should work on multiple platforms? How long would it take you to create such a library in C that provided a single interface for clients of your library to use?

Well maybe he can't buy any of these arguments because he said himself that Boost is neither portable nor stable (wrong on both counts but oh well). What if you need to write a non-portable app that preprocesses C or C++? Oh hey, I can do that in a few lines of code with Boost.Wave.


Okay, enough about Linus' complaints with C++. How about some more general complaints that aren't directly related to how C is better than C++.

http://damienkatz.net/2004/08/why-c-sucks.html

Here is a blog that explains that C++ sucks because he couldn't find an elegant solution to manage HMODULEs (which for those who don't know anything about windows are sort of like a specialized type of file descriptor). I guess the author isn't familiar with reference counting though, because a shared_ptr<> solves this problem immediately with no extra work involved.

http://www.dacris.com/blog/2009/02/10/WhyCSucksSortOf.aspx

Here is a blog that explains that C++ (kind of) sucks for a bunch of reasons, all of which are, as usual, invalid. In this particular blog, the author is only comparing it with Java.

Ant vs. make -
Completely irrelevant. You can use Ant with C++ projects, and you can use make with Java projects.

Pointers - "Do you really need pointers? Do you really need to convert integers (memory addresses) to objects?"
Yes actually, I do. I guess the author has never worked on embedded software or real time systems. Or games for that matter. Or I guess anything other than canned business software.

Try making a game in C++ / DirectX and then try doing the same game in XNA implemented using C#. Make a scene with a single model and write some code that determines, when the user clicked the mouse, if an object was intersected and if so which point on the object was intersected. In C++ this is trivial. You have direct access to the Vertex Buffer you can simply offset into it, cast it to a pointer, and you're done. In XNA using C#, there is no good way to do this. The recommended way, which is pretty freaking awful (although admittedly very simple), is to write an fairly simple extension to the build system that augments the model with easily accessible vertex information. But here's the thing: The model already contains all the vertex information in the vertex buffer. Doing this approach at a *minimum* doubles the amount of storage required for a model's vertex information. You're storing the exact same information twice.

"Are you willing to put up with the hassle of manually collecting garbage (using delete statements)?"
Actually no, I'm not. That's why I don't do it in C++. Instead, I use reference counted pointers and automatically scoped pointers about 99% of the time. I actually don't remember the last time I manually invoked the delete keyword, but I invoke the new keyword constantly.

"How are you going to test that you don't have memory leaks?"
Sorry, are you trying to imply that Java and other managed languages don't have memory leaks? Because this is certainly not the case. If you leave a dangling reference around, you have memory leaks in Java and any other garbage collected language. In any case, I'll probably test that I don't have memory leaks the same way as I would in a managed language: by using a profiler.

CPU Architecture and OS -
"What if the architecture you're working on uses 48-bit memory addresses and you want to port your program to an architecture that uses 64-bit memory addresses? Have you thought about how you're going to do that?"
Of course, although I think a better example would have been 32 and 64-bit. C++ has a keyword called sizeof() that lets you determine how big a pointer is. Admittedly it requires more care than in managed languages, but I really don't remember the last time I saw an actual programmer using magic numbers in their code. I suppose it happens though, maybe this is related to Linus' "substandard programmers"?

"What CPU are you programming for?"
Doesn't matter. We're talking about C++, not assembly.

"What OS?"
Rarely matters. I'm using Boost and STL. The times where it does matter, Java wouldn't help me anyway. For example, my application deals with the filesystem and I need to be able to manage all types of objects that might exist on the filesystem. On windows this means Junctions, Symbolic Links, Hard Links, etc. On Linux this means Pipes, Sockets, Block Devices, Char Devices, etc. Does Java abstract all of this out for me so that I don't need to know what Operating System I'm on?

"Are you going to use Unicode?"
One of the only points in this blog I agree with. But only a little. C++0x has full unicode support.
JUnit - "Unit testing: How will you do it in C++?"
Probably using Boost.Unit.

Graphical User Interface -
"Suppose you want a GUI for your application. Which one will you use? Gtk, Qt, Win32, MFC, .NET?"
I'm not sure if this is a serious question. I thought we were talking about C++, why is .NET listed? Win32 and MFC are clearly platform specific, so I obviously won't be using them if cross-platform is a concern. Gtk and Qt are both fine and both do a decent job emulating a native GUI. So, again I'm not sure if this is a serious question.

Perhaps an even better answer is in order though. I won't use any Gui toolkit, because I won't develop GUI code in C++. Why would I use the language for something which it doesn't excel at? That's stupid. A good programmer knows how to make use of many different tools and picks the one most appropriate for the job. If I only cared about Windows I'd use a .NET gui and invoke my C++ code through C++/CLI or PInvoke. If I needed a cross-platform GUI I'd write it in Java and use JNI. There are plenty of things that Java doesn't excel at either, why aren't you talking about how to write Java code that deals with unsigned types, whether they be stored in a database, a file, or coming from the network or some server? probably because it's a freaking nightmare in Java, and of course there are languages much more suitable for this type of work if it's a huge part of your application.

Web Applications -
"What are you going to do about web applications?"
Nothing. I'll use .NET, Java, or something else more appropriate. Again with the one-size-fits-all.

Dynamic Linking - "Suppose you want to enable your application to have plugins, so that other developers can contribute parts of your application without seeing the core application's source code. How exactly are you going to do this? In Windows, you can use DLLs. What about in Linux? How are you going to distinguish between the two?"
The more I read this the more I realize the author just doesn't have any experience with C++. We're working off the assumption that this app is cross platform. So how hard is it to require that the plugin be cross-platform too?

In this hypothetical situation I'm already producing a different binary for both Windows and Linux, each of which uses some platform specific code (even though except in specialized situations all that platform specific code is hidden in the depths of Boost, STL, or some other 3rd party library). Is it that hard to just say that plugin compiles on both platforms as well? It then exposes a simple C-interface of routines that the main application dynamically links to (LoadLibrary/GetProcAddress on windows, dlopen/dlsym on Linux). Problem solved, simple.

Exceptions vs. Core Dumps -
"Would you rather receive an exception or a core dump when something goes seriously wrong in your application?"
Obviously a core dump. But is this a hypothetical application that has no logging, where we must make a mutually exclusive choice between exceptions and core dumps? All production quality apps would be writing to a log file and keeping track of anything unexpected. Despite that, I agree core dumps are more useful if you have to have one or the other. But this poster seems to be arguing that if Java is better at anything then C++ must be terrible. By that same logic, if C++ is better at anything, then Java must be terrible. Well C++ is better at things. One example is interfacing directly with the O/S.

In conclusion, neither language is terrible. They just excel at different things.


I think I should at least mention a few of my own thoughts on why C++ doesn't suck -- some advantages of C++ over other languages if you will.

C++ has a very strong, flexible type system.
The word 'painfully' certainly doesn't instill confidence in how awesome something is, but in the case of a type system the stronger the better. The more things the compiler can do for you the better the code will be. Sure, there are certain problems that just flat out can't be solved at compile-time, like catching buffer overruns, but it's well-known that type-problems represent a massive percentage of logic errors.

I know what you're thinking. "Are you f**king kidding me? You can cast arbitrary objects to pointers of any other type, and you call that strong?" Well, err.. No actually. That's a clear violation of a strong type system. But I've always subscribed to the philosophy that when you need a jackhammer, it's really nice to have a jackhammer sitting around, even if it's dusty.

So what do I mean by a strong type system then? The const keyword is one of the most excellent examples. It's one of the reasons I feel uncomfortable in almost every other language, no matter how well I understand the syntax and paradigms. It is really, really, really useful to have the compiler enforce that the state of certain objects cannot be modified. I absolutely hate the fact that Java and C# don't have an equivalent of this.

Haskell and ML have this automatically since they're functional languages and you don't modify values anyway you create new values, and this is such a powerful feature for the same reason that functional languages are so powerful. Not being able to modify state is actual the fundamental differentiating factor of functional languages and what makes them as unique and powerful as they are, and in a way the 'const' keyword in C++ brings us ever so slightly closer to this.

From a design standpoint, it's terrible knowing that 100% of the time, if I pass an instance of a class to a function, I have no guarantee about the state of that object after the function returns. I'd love to hear about other languages that support this type of type checking, I can't think of any off the top of my head.

Code Generation
I've already talked about this a little above, but code generation really takes generic programming to the next level. Of course, there is always a tradeoff. In C++ the tradeoff is difficult to diagnose error messages and easily misunderstood/misused features.

Difficult to diagnose error messages should eventually be a thing of the past, particularly if Concepts ever make it into C++0x as a technical report or something, although it's definitely annoying in the meantime. With some practice though you begin to easily be able to skip over all the BS and figure out the meat of an error message, especially if you frequently use the same compiler.

Misunderstood/misuse of features is the biggest issue when it comes to generic programming in C++, and it can definitely be a project killer in extreme cases (I've worked on projects that were literally killed because of this). Like I said earlier, coding standards should be tailored to the lowest common denominator of programmer that will be on a given team. C++ is a more advanced language than it was 6 or 7 years ago. I agree it's just not appropriate for certain people to be using all of its functionality.

The real issue here is that "old-style" C++ programmers are still trying to program in C++ and applying old-style C++ practices to codebases in which new-style C++ methodologies are being used. This is a bit of a fundamental problem, in some respects it would have been nice if the current evolution of C++ were just a completely new language that was simply not backwards compatible with old C++. I agree this is a real problem though, and probably the biggest problem facing C++ currently. The best solution until we see what the future has in store is to know the programmers on your team.


So, where is all of this leading? I concede that C++ is growing increasingly difficult to master and it takes a certain breed of programmer to be able to deal with all of its intricacies. I too have been burned by the terrible template programmer who ends up leaving me re-writing his entire framework because he came up with a bad design using templates. Does this mean C++ sucks? No, it just means that your coding standard should be somewhat tied to the lowest common denominator of C++ programmer on your team .

On that note, nobody, and I mean nobody should be using C++ without Boost. I'm serious, it should be just as required to use Boost as it is to uses classes and object-oriented design. I'm not going to sit around and argue about the pros and cons of object-oriented design (of which it has both, just like anything else in the world), so if you reject that objects are useful then be my guest.

Is Boost complicated? Yes, it is. But it is possible to realize massive benefits from boost while only using an extremely small subset of the available functionality. If all you use is boost::shared_ptr you already gain massive benefits. It's honestly a little insane to completely reject boost.

C++0x takes this one step further and eliminates even more of the complexity currently involved in using C++. things like shared_ptr and threads are built into the language. Unicode support is built into the language.

In the past I would say that C++'s main advantage has been viewed as being very close to the OS while still providing a reasonable model of object oriented design. I would say that this is changing. C++'s main strength is starting to become it's ability to express generic code. Even better, you don 't have to be an expert in *writing* generic code to take advantage of this. Leave that to the academics and the library designers if you think it's too complicated. All you have to do is use what's already out there. If you have neither the need for generic code nor the need to be close to the operating system, obviously there's going to be a more appropriate language for you. If you have the need for both of these (or even for just one of them), then C++ is still very appropriate. I think you'll be hard pressed to find an application that doesn't benefit from generic programming though.

C++ is certainly rather verbose at expressing highly generic code, much moreso than its highly generic competitors like Haskell or ML so if *all* you care about is genericity then one of those languages might be more appropriate. But in some ways, C++ is actually more generic than these languages just due to its code generation abilities. But those languages are completely dead in the water if you need to program close to the operating system, as are most other languages except C. C gets even uglier and more verbose though when you start trying to go cross platform. Even if you aren't an expert at creating generic code using C++ templates and boost::mpl, it doesn't take a genius to be an expert at using generic code.

1 comments:

Unknown February 15, 2010 at 1:28 PM  

" I'd love to hear about other languages that support this type of type checking, I can't think of any off the top of my head."

Have you had a look at D? D 2.0 goes even further than C++ in this regard I believe. It sucks that there's nothing as good as Boost available for D, or I might have been tempted to say that D is better than C++.

Excellent article btw, I enjoyed reading it.

  © Blogger templates ProBlogger Template by Ourblogtemplates.com 2008

Back to TOP