Why C++
刘未鹏(pongba)
C++的罗浮宫(http://blog.csdn.net/pongba)
非常感谢waterwalk翻译了整篇文章,我整理了之后单独贴了出来,见这里。
The Problem
So, why C++? Before you frown and turn away. Just try to answer this simple question.
Efficiency, right? Everybody knows the answer. But as it turned out, when discussing a programming language or everything related to one, one should be very specific. Now why’s that? Let me ask you another question: if efficiency is the only reason people use C++, then why don’t they just use C? C is admittedly more efficient than C++ (yeah, yeah, I know it has been proved that C isn’t to any significant extent more efficient than C++, so don’t get me wrong here, because even if they are equally efficient, the problem still exists).
The Myth
I know you are going to say “better abstraction mechanism”, because after all C++ is designed to be a better C, one that has uncompromised efficiency and yet at the same time has all those fancy high-level features. But then the problem comes down to “does it really matter if the developers need those fancy features?” I mean, after all we all have been hearing voices about KISS and stuff, and we all have heard about the claim that, compared to C++, C is more KISS so we should use C. This unstoppable argument has turned the comparison between C and C++ into a big myth (or maybe a mess). And surprisingly, it seems that many people do incline to C, the reason mostly being that C++ is so hard to use right. Even Linus thinks so, too.
The real serious impact of this phenomenon is that it drives more people to C when they’re weighing their options, be them C and C++; and once they start using C, they will soon get satisfied and comfortable with what suffices, experiencing what is called “satisfaction”. This is when they will come out and claim that C actually is a better choice than C++ even though they didn’t actually try to use C++ or they aren’t adequately good C++ programmers at all. The real answer, however, almost always begins with “it depends”.
So, did I say “it depends”? On what? Obviously there’re some areas where C is a better choice than C++. For instance, device driver development is usually something that doesn’t need fancy OOP/GP techniques. It’s just simple data manipulation; what really matters is the programmers know exactly how the system works, and what they’re doing. Now what about OS development? I’m not a guy who’s been involved in any kind of OS development myself, but having read a fair amount of OS code (Unix mostly), I’ve come to feel that there’s a significant part of the OS development that doesn’t need OOP/GP either.
However, does that mean that, in all those areas where efficiency matters, C is a better choice than C++? Not really.
The Answer
Let’s do this case by case.
First of all, when people are concerned about efficiency, there’re really two kinds of efficiency – time efficiency (e.g. OS, runtime, real-time applications, high-demanding systems) and space efficiency (e.g. all sorts of embedded systems). However, this categorization doesn’t really help us determine whether we should use C or C++, because C and C++ are both extremely efficient as to both time and space. What really affects our language choice (between C and C++, of course) is the business logic (here by “business”, I don’t mean the “enterprise application business”). For example, is it better to use OOP/GP to express the logic or is it better off being kept pretty much just about data and procedures.
From this point of view, we can vaguely divide applications into two categories (of course, with the premise that what we’re concerned with is C/C++, not java/c#/ruby/erlang etc.): low-level applications and high-level applications, where low-level applications means the ones where fancy abstractions such as OB/OOP and GP are pretty much of no use, and high-level means all the rest. Now, obviously, of all the areas where C/C++ is used (because of their high-efficiency), there’re a significant number of “high-level” applications (see those listed on Bjarne Stroustrup’s homepage), where abstraction is just as important as, if not more important than efficiency. And those are precisely the places where C++ is used and useful in a unique sense, and where C++ is a better choice than C.
Wait, there’s more. As it turns out, even in those areas where programmers don’t use high-level abstractions in their code per se, there might be a reason they should use C++, too. Why’s that? Just because your code don’t use class or templates doesn’t mean it doesn’t use a library that does. Considering the availability of all the handy C++ library facilities (with tr1/tr2 coming soon), I think there’s a pretty strong reason to use C++ in these cases - you can stick to the C core of C++ when coding (KISS in any way you want), and at the same time you’ve got some awesome C++ libraries at your disposal (e.g. STL containers and algorithms, tr1/tr2 components, etc.). And finally, there’s this one thing that’s always ignored by many people – sometimes KISS relies on abstractions. I think Matthew Wilson made a crystal clear point about this in the prologue of his new book “Extended STL, Vol 1”, where he laid down two blocks of code, one written in C and one in C++:
// in C
DIR* dir = opendir(".");
if(NULL != dir)
{
struct dirent* de;
for(; NULL != (de = readdir(dir)); )
{
struct stat st;
if( 0 == stat(de->d_name, &st) &&
S_IFREG == (st.st_mode & S_IFMT))
{
remove(de->d_name);
}
}
closedir(dir);
}
// in C++
readdir_sequence entries(".", readdir_sequence::files);
std::for_each(entries.begin(), entries.end(), ::remove);
And it’s even simpler in C++09:
// in C++09
std::for_each(readdir_sequence(".", readdir_sequence::files), ::remove);
I think this is exactly the reason why one should use C++ even in those cases where he doesn’t really need class or templates in his own code – the handy C++ libraries he will find very useful does. Similarly, if an efficient container (or a smart pointer) will save you from all the boring job of manual manipulation of memory, then what’s the point of using the primitive malloc/free? If a better string class (I’m not talking about std::string; everybody knows it’s not the best C++ can do) or regex class can relieve you of all the cluttered string-manipulation code you don’t even want to look at, then what’s the point of doing it manually. If a ‘transform’ (or a ‘for_each’) can do your job in one line so succinctly and clearly (and I know, of course, C++ need lambda function support for those – that’s what C++0x is for), then what’s the point of hand-written for-loops? If high-order function is really what you need, then what’s the point of using awkward workarounds to approach the same deal?
KISS doesn’t mean “primitive”; KISS means using the most suitable tool for your job, where “most suitable” means the tool you use should help you express your mind as straight (and succinct) as possible, as long as it doesn’t compromise the readability and understandability of the code.
The Real Problem
People might say that C++ is much more easily misused than properly-used, and C, on the other hand, is always more manageable and controllable as to complexity. In C++, an average programmer might come up with a whole bunch of highly coupled classes that degenerates fast into a big mess. But this is actually a separate issue. On the one hand, it can pretty much occur in any object oriented language. There’re always programmers who dare to write classes on top of classes even before they have any idea what HAS-A is and what IS-A is; they learn all the syntax of defining a class and inheriting one from another and they thought they’ve grasped the essence of OOP. On the other hand, the reason it appears to be more serious in C++ is because C++ has so many accidental complexities that impede the design, and because C++ is so flexible that pretty much every problem in C++ has several alternative solutions (thinking of all the GUI libraries) so that weighing all the options becomes a hard job itself. The accidental complexities are a historical baggage that C++0x is trying so hard to (and hopefully will) get rid of; the flexibility with respect to design isn’t actually a bad thing if you think about it - it helps good designers make good designs; and if someone blame them for hurting his brain then maybe it’s his problem, not the language’s; maybe he shouldn’t be the one to make a design. And if you’re so worried that your fellow C++ coders will be enticed by fancy high-level features and that your project will eventually get screwed, then maybe what you should do is setting up a coding standard and enforce it (or you can just follow the collective wisdom, or stick to the C core or C with class part of C++ if necessary), not flinching away just because there’re risks (risks that can be avoided by policies), because then you will not be able to access all the C++ libraries anymore, mind you.
On the other hand, there’s this more important psychological problem – if there’s a bizarreness in a language, then eventually someone will find it and people will be attracted by it, and it will draw energy from the main people effort of doing something really useful (It’s kind of like the Murphy's Law), let alone the ones that can lead to an (on some level) elegant solution to a real problem. People are inherently attracted by scarce resources. Corollary: Tricks and bizarrenesses are scarce resources, so they draw people’s attention, not to mention the fact that mastering a trick makes one feel special in the herd. The bottom line is, even useless tricks draw people’s attention so heavily.
How many black corners are there in C++? How many tricks are there in C++? All in all, how many accidental complexities are there in C++?
To be fair, most of the tricks and (you might say) techniques that have been discovered in recent years (i.e. modern C++) are driven by real needs, particularly the needs to implement highly flexible and generic library components (thinking of all the components in boost). And they did lead to (on some level) elegant solutions to real problems. Think about it this way: if you’re put in a place where either you have to use tricks to implement something really useful or you don’t implement it so other people won’t have the benefit of using it. What would you choose? I know that the boost heroes chose the former – implementing them, no matter how hard and tricky and cumbersome the implementation is.
But all those arguments don’t change the fact that we deserve to have a language that supports a clean way to express our minds in code. Take boost.function/boost.bind/boost.tuple for examples, variadic templates will tremendously simplify (by reducing the LOC to nearly 1/10 of the original) the implementation of the three (and many, many more to come) libraries, and the code will become succinct and as simple as possible, too. Auto, initializer-list, rvalue-reference, template-aliasing, strong-typed enums, delegating-constructors, constexpr, alignments, inheriting-constructors, etc; all those C++0x features, they all have one goal – eliminating the various accidental complexities or embarrassments of the language.
As Bjarne Stroustrup said, obviously C++ is too complicated; obviously people get scared and sometimes turn away. But “people need relatively complex language to deal with absolutely complex problems”. We can’t make a language more powerful by taking features away from it. Complex features like templates and even multiple-inheritance can be useful if they’re exactly what you need, you just have to use them very carefully and by necessity so that you don’t shoot yourself in the foot. Of all the complexities in C++, the ones that really get in our way are the accidental complexities (someone might call them “embarrassments”), not the paradigms the language supports (there’re only three). And that’s a very important reason why we should embrace C++0x, because it aims at eliminating the long standing accidental complexities C++ had and make obsolete all the arcane tricks (there’s absolutely huge amount of them out there; check all the C++ books and maybe the boost library and you’ll know what I’m talking about) so that we can express our mind clearly and directly.
The Conclusion
C++ is hard, and even harder to use correctly. So when you decide to use it, be careful, always know where you are and what you really want. Here’s a simple guideline:
Do we need to be efficient?
If so, then
Do we need abstractions in our code (think very carefully on this one, because it’s very hard to estimate whether the benefit of using the high-level features of C++ outweighs the risk of using them incorrectly; the proper answer depends on how well trained your programmers are, what coding standard you follow and how well it’s enforced, etc.)?
If so, then use C++. Otherwise,
Do we need good C++ libraries to ease our job?
If so, then use C++, but meanwhile always remember what you are doing – if your code doesn’t really need all the fancy abstractions, then try not to get sucked into them; don’t use class or templates just because you’re writing code in a .cpp file and using a C++ compiler.
Otherwise, use C, but then you might wonder why not just use the C core of C++. The same reason as always: people get easily sucked into fancy language features even when they don’t really know if they’re going to help – I can’t tell you how many times I wrote a bunch of classes only to find out “what the heck are these classes for?”. So, if you can stick to the C core or C with class part of C++ and keep simple things simple, or if your code needs a migration path from C to C++, use C++ then, but be very careful. On the other hand, if you need neither abstraction mechanisms in your code nor quality C++ libraries because what you’re doing is so simple that you don’t even need convenient components like containers or strings, or you decide that the benefit C++ can bring you in your project is minor to an extent that it’s not even worth taking the risk, or you just simple don’t have enough people that can use C++ in a proper way, then maybe you should stick to C.
The bottom line: keep simple things simple (but remember that simplicity can be achieved by using high-level libraries); use abstractions when necessary (and even then, make spare use of it; follow good design principles and established good practices).
--