The biggest problem with C++ (in my opinion) is a lack of a good library manager.
As installing C++ libraries is a pain, and testing them on a large range of compilers is a pain too, the only library of note anyone ever uses is boost.
Most of Boost is a horrible templated mess. I consider myself fairly expert on C++, have contributed code to both clang and g++, and worked on C++11. I still don't use boost, and am slowly taking it out of projects which it has infected.
Boost is interesting as seeing how the bounds of C++ can be pushed, but as a library for day-to-day use it is horrible. It is just about usable if you limit yourself to a few small pieces. For example at first glance boost::spirit is a really nice parser library. However the error messages give me shivers to this day and a parser I wrote using it takes over 1GB of memory and 40 seconds to compile.
Please tell what is wrong with libraries such a datetime, regex, filesystem, bind, shared_ptr, string, test, uuid, just to name the ones I use regularly. Also please tell what you would suggest as replacements, and why they do not share the deficiencies the boost versions do according to you. It's easy to hate on templates, and not is all perfect in C++ or boost, but boost is the best thing to happen to C++ to make it easier to use, or rather to alleviate some of the most annoying and unnecessary drawbacks of the language. I'm not questioning your expertise, I'll take your word for the accomplishments you mention - but your apparent disdain for it is, to put it mildly, lightly substantiated and shows more signs of ideologically fueled hatred than of a deep understanding of the negative sides of something that can only be developed through much experience.
And maybe they shouldn't like shared_ptr so much! It's a source of memory lifecycle bugs; everything touched by shared_ptr needs to be under the shared_ptr regime or carefully bridged to it.
boost::bind seems more like evidence in the case against C++ than an advertisement for boost.
"everything touched by shared_ptr needs to be under the shared_ptr regime or carefully bridged to it"
Well, yes. That's sort of the point. You can write old-style C++, or your can write modern C++, but the library designers are trying to make you think carefully when you're doing both, because that is a source of memory lifecycle bugs. Using the boost (now standard) pointers universally tends to dramatically reduce the chances of memory-related bugs in C++ code.
Not using shared_ptr is a code smell. Sometimes you have to do it, but if you're avoiding the use of the smart pointer classes in blue sky code, you're doing something wrong.
The main thing that sucks about shared_ptr's is that they break C++'s limited support for covariance. For example
class A { ... };
class B : public A { ... };
// Totally works
class AFactory {
public:
virtual A* makeOne();
}
class BFactory : public AFactory {
public:
virtual B* makeOne();
}
// totally doesn't
class SharedAFactory {
public:
virtual boost::shared_ptr<A> makeOne();
};
class SharedBFactory {
public:
virtual boost::shared_ptr<B> makeOne();
};
Also, from a performance perspective, locking the memory bus to do atomic operations on a shared_ptr totally blows when you're expected to turn a request around in a few microseconds.
I'll grant you the larger point that inferring relationships by template value type can be a pain. But this example is kind of bad. First off, I assume you meant to derive SharedBFactory from SharedAFactory? Otherwise, it works fine.
If not, the only problem here is that C++ won't let you have a virtual function override that only differs by return type. And since it can't tell that shared_ptr<A> and shared_ptr<B> are related by value_type, it complains (this is the larger point that I granted you earlier). But the type theatrics are hiding a bigger problem....
In this example, you can either fix the type error by making makeOne() return a boost::shared_ptr<A> in all factories (in which case, you really do want a polymorphic interface), or you can make a new function that returns a different pointer type (in which case, you don't). The type system here is doing you a favor, and telling you that your design is bogus -- you shouldn't be creating a polymorphic factory interface that returns different types (because that isn't actually polymorphic, is it?)
This isn't totally academic: I find that in nearly all type-error situations like this one, the real problem can be traced back to sloppy coding.
"locking the memory bus to do atomic operations on a shared_ptr totally blows when you're expected to turn a request around in a few microseconds."
There's always a cost to reference counting, and therefore, it's not always appropriate. That said, the only time you should be triggering atomic operations are on pointer copies and allocations. If you're doing allocations per request, kiss your microsecond response time goodbye -- the reference count overhead is the least of your worries. And you can avoid unnecessary pointer copies by passing shared_ptr<> objects by reference. With a small amount of care, it's possible to reduce shared_ptr<> overhead to essentially zero.
Their polymorphic factory isn't returning "different types." They're both returning A. The derived one is just returning a specific type of A. If the shared "makeOne" returned naked pointer types A* and B* rather than shared_ptr, then this would all work correctly, thanks to C++'s covariant return type support.
Covariant return types are a powerful feature in a language that doesn't support other forms of return type polymorphism. shared_ptrs function so similarly to pointers that it can be surprising when they don't support this behavior. Surprise and capability mismatch with pointers are strikes against shared_ptr. But not sufficient ones to stop me from using them.
No, they're not. One is returning a pointer to A, and the other is returning a pointer to B (which just happens to be a subclass of A). It matters, and just because you can do it with a naked pointer doesn't mean that you should. The problem you've described is a code smell.
If I hand you a pointer to B, you (client code) can do everything that is exposed by B. If I hand you a pointer to A, you can only do the set of things exposed by A. And that's a fundamentally different interface guarantee.
There are cases where covariant return types are abused, so it's fine that you treat it as a code smell. However, C++ does make them necessary for several valid patterns. This does include alexgartrell's example, but here's a popular one:
I have a polymorphic type that should be copyable. Because of the limitations of C++ copy constructors and operators, my only option is to expose a virtual method (call it "copy"). The non-smelly semantics we want are such that if you call "copy" on an object, you get an identical copy of the same type. So if you call "copy" on a pointer of type A, you get an A. Call it on a B, you get a B.
The contract itself is polymorphic. It doesn't commit to returning any particular flavor of A.
We can implement this method if we return naked pointers. But if we want to be safe and return a smart pointer, we have to introduce dynamic casting or other worse smells.
"I have a polymorphic type that should be copyable. Because of the limitations of C++ copy constructors and operators, my only option is to expose a virtual method (call it "copy"). The non-smelly semantics we want are such that if you call "copy" on an object, you get an identical copy of the same type."
Well, yeah...because those semantics are just as smelly as the one you described before. What you describe isn't a copy constructor at all, and trying to make a copy constructor do what you want is a dangerous thing. A "constructor" that takes any subclass of class A and returns an instance of that subclass is inherently brittle: add a new subclass of A, and you've got to update the "constructor" to know about the new type. On top of that, your "constructor" has to introspect into the type to determine what to initialize when, and that's slow.
There are easy ways to get the behavior your want (e.g. make a template function that calls the appropriate class' copy constructor, or -- closest to what you want -- make a template copy constructor function on the class itself), but complaining that you can't make a bog-standard copy constructor do polymorphic construction kind of misses the point.
Here's a better discussion of the problem than I have the space to go into here:
We're going too deep into this, for alexgartrell's relatively simple claim that covariant return types have a reason for being in the language, and that smart pointers omit that capability.
But just to clarify: no base class implementation is aware of derived classes in the copying case, so your smell doesn't exist. See Scott Meyers' "virtual copy constructor" snippet half way down the page:
http://books.google.com/books?id=azvE8V0c-mYC&lpg=PT159&...
You can't do it that neatly without covariant return types. Ergo, you can't do that while returning safe, smart pointers.
I'm tired of marketing boost as "modern C++". Alexei Alexandrescu, the author of "Modern C++ design" book has since gone on greener pastures and got involved with the D language. He participated in the design of D, and wrote a book about it.
Not using shared_ptr a code smell? If you're not able to keep track of your objects yourself, you should not be using C or C++. It's not that hard like you would like us believe.
Also, apart from the cost of maintaining reference counts, shared_ptr makes an extra allocation for the control block. Unless you take care and use make_shared, you end up with allocations that are twice as slow.
On a cynical side: Boost has become Dave Abrahams' business (1) He (and co.) have a vested interest in making you believe that the Boost-way is "the" way of writing "modern" (as defined by HIM and his followers) C++ code. I just wonder whether he had that much foresight, or just took the chance when he saw the opportunity.
(1) I remember once asking about obtaining slides about fusion from a past BoostCon conference and got a blank "no". Slides were available only to the people who paid to attend the conference.
Yes. It's part of the C++ standard. All rants against Dave Abrahams and Boost aside, not using the standard library to do standard things is a code smell.
What "standard things"? shared_ptr is only one of many possible ways to keep track of object lifetimes. Rolling your own reference-counted smart pointer might be a code smell, but not choosing reference counting is far from that.
It doesn't "break"...you just have to think about what you're doing. Which, again, is the point.
If some external code is passing you pointers, then using shared_ptr is exactly the wrong thing to do, because you (presumably) don't own the memory. And if you do own the memory, there's no problem putting it in a shared_ptr. The fact that you can't blindly stuff everything into a shared_ptr is a feature, not a bug.
If your program fits the *_ptr mold (i.e. no unbreakable cycles) and you use it consistently it does tend to be nice. Almost like having automatic memory management.
Anyone care to elaborate on the evils of boost::bind? Maybe from experience?
I used boost::bind and boost::function in VC6 code of all places and I thought that the resulting code turned out to be pretty decent (I think newer compilers let you use slightly less verbose syntax), and I really liked having the functionality of being able to pre-bind certain arguments. For example, I had a menu item click handler that I was able to bind a call to a unary method, but with different parameters. I thought the resulting code was simpler overall (and easier to refactor).
Well IIRC it was a tempting way to replace for-loops with direct list operations ala Python, Ruby etc. But the amount of syntactic noise and cognitive load seemed too much for the added leverage.
Our code had sufficient complexity that I didn't want one more oddity (as many would see it) to explain.
But for a small team, or a team that's all on the same page, maybe it's good.
Sorry to say there's no dramatic story of code explosion here!
I wouldn't use it for for-loops either, but man, boost::bind is so handy when compiled with boost::function! I used to use function pointers and 'void *userData'. If I wanted to pass additional data to the function then I have to allocate a structure and not forgetting to free it later which resulted in a lot of boilerplate code.
First of all, my original post was writen late at night, and I should perhaps have given more care to it.
There are some good parts of boost. shared_ptr, bind and regex are now in the C++ standard library.
uuid is an interesting example, as it really doesn't have any reason to be 'in' boost. It is a small, self-contained library which (I understand) does one thing very well. In any other language it would just be in the standard package repository. However, for some reason in C++, instead everyone wants to get into this one bundle of packages.
The problem is, because boost is monolithic, the temptation to developers is to start pulling in some boost::spirit, or boost::phoenix. I find these libraries fascinating from the point of view of what can be achieved in C++, but for day to day maintenance they cause serious problems.
The problem (which I could have better articulated) is that the messier, templated parts of boost end up being seen as 'best practice', or libraries that people should use. I wold much prefer something which lets me easily install just uuid, or boost::optional (another great tiny boost library).
There are of course various solutions. I could install a stripped-down boost, or just introduce strict coding standards.
Sometimes, you can see the negatives straight away. No experience necessary! A 40 second compile time is a pretty big negative. I wouldn't even suggest this is exaggeration; my experience with boost is not that wide-ranging, but what experience I have does include waiting for programs to compile.
(Maybe experience is even the problem. A C++ programmer might suck up a 40 second round trip time, but I bet no PHP or Python programmer would!)
Large C++ projects already tend to suffer from monstrous compile and link times. Anything that increases build times is inherently a bad idea. Iteration time has been THE major issue on every project I've worked on. Sucks up man-hours like nobody's business. All the other C++ junk... borderline irrelevant, by comparison.
There in lies the problem boost is a collection of libraries that are more or less unrelated to each other. So sure spirit is a major pile of template catastrophe, as are some of the others (boost::lambda was my introduction to something I should never do with templates.) Then you have the others. FileSystem has nary a template in sight, Asio and Thread have a few but they aren't crazy.
The presence of "octonions" in the boost library kept me from seriously considering it for like 2 years. I understand the rationale--that by making it monolithic they're getting more libraries installed on more developer's machines--but come on. If I need threads, filesystem, date_time, foreach, etc. don't make me install wave, spirit, proto, phoenix, fusion, fucking octonions.
> Anything that increases build times is inherently a bad idea. Iteration time has been THE major issue on every project I've worked on. Sucks up man-hours like nobody's business.
I'd say this is only true part of the time. Optimizing for developers' time (thus, payroll cost) is a good idea, but not to the exclusion of other costs. For instance, if you're building a web app that's grown to the point where the business's primary cost is the hardware on which the software runs, it makes sense to make technology decisions with that in mind. Trading some developer efficiency for lower data center costs is perfectly reasonable.
Not to mention link times: templates generate HUGE mangled symbols, with corresponding increase in memory and time as all those strings have to be stored and string-compared.
I've long wondered about why doesn't any C++ compiler implement symbol hashing? As in: replace symbol names with symbol hashes (SHA1) in the symbol table, and emit an extra section that maps hashes back to mangled names. Linker could do all of its work using hashes, and would map them back to regular symbol names only when generating the final debug info file.
The OCaml compiler uses hashes for some symbols. It has to have extra discrimination code in the linker to deal with potential collisions, and it can reject valid programs because of this. That's in theory, because I've never seen a program rejected, except for a synthetic program that someone made to demonstrate hash collisions ...
It is not as fast as it could be. It is an implementation of ordinary hash table, meaning that also a successful search requires comparing two strings several kB long. What I propose is to reduce ALL symbols to their hashes (SHA1 is 20 bytes) and to "dehash" them only after linking has been done.
Collision in hashes is of only theoretical interest when you consider SHA1 or SHA256.
I don't know how large your project is, but you could use a parallel build solution such as Icecream to dramatically shorten your build time, especially if your project is composed of numerous compilation targets.
I consider Qt to be more a framework than a library per se. The fact of being written in C++ is probably irrelevant, since you usually don't link to it from other projects (say ncurses, gtk based), you use it to build a Qt application or extend it (like the KDE project).
With boost you usually pick and choose and include parts of the (templated) libraries directly into your project directory, so you don't have to embrace it in full.
Sorry, but no. You can use libQtCore or libQtGui without the rest. Or just libQtCore + libQtSQL for a server. You can even mix them inside an application that is not a QApplication.
I would say the fault lands on all three. The language could have been designed such that good error messages where easier to output. The library authors could have made an attempt to force better error messages. Finally compilers could get their act together and output good error messages.
Are you just saying you find the implementation distasteful? I don't find using the templated boost stuff problematic. Most of the time you just cargo cult examples from the documentation and all's good, but then you have great flexibility should you need it.
> you just cargo cult examples from the documentation
This mindset, and libraries that are basically only usable in that way if you don't want to invest months of your time are the bane of programming. Library writers, please:
1. Design the API by thinking about how it will be used, not how it will be implemented.
2. Make sure that your publicly exposed interface can be specified simply. If your specification is anywhere near as complex as the code implementing it you know you're doing something wrong; your library is not reducing complexity.
The complicated parts of boost where this is what you end up doing are really more like libraries for library writers. You'd almost certainly wrap something like Boost.Geometry for your specific needs. It's made with flexibility as the goal. So it's really not the problem you're presenting. The alternative is dozens of different half-baked libraries for specific use cases.
Please enunciate why something like asio is evil. You review the documentation and examples and the outlined primary use cases "just work". But you can dive in and do really complicated things.
I don't think any of Boost is "evil", and I haven't mentioned asio --- asio came out after I was done using Boost. But I agree with you that a lot of C++ code works because it's part of a cargo-cult culture, not because the interfaces themselves are particularly well- thought- out.
In my experience writing C++ as a hobbyist & minor professional work, it leads to complexity for a couple reasons.
- Lack of higher order functions generates boilerplate classes for fairly simplistic closure-istic operations
- Using templates with any sophistication infects gobblygooky templating syntax around your codebase. Since templates are actually a really nifty concept and worth using, this happens pretty quickly and now your codebase has more angle brackets than braces. ;)
- Inability to add syntactic abstraction means everything is written out, all the time.
I spent a lot of time learning C++98. After I got exposed to other languages, I don't think I'm going back to C++ without a good reason. There's too much ceremony for too little gain. Arguably with sufficient blood, sweat, and tears libraries can be written to make C++ do or fake what other languages do easily, e.g., regex, closures or continuations.
I think that taking the dynamic language approach of C in the performance parts and dynamic language for the rest is a better way to architect your system. Or, select a dynamic language that compiles to native code. :-)
Inability to add syntactic abstraction means everything is written out, all the time.
I disagree with that one. Of all popular languages, only Python and Ruby (maybe C#) come anywhere close to the level of syntactic abstraction that C++ supports.
I will go as far as to say (from experience) that releasing a library in C++ to others is completely foolish due to the major problems it can create when design changes are inevitably needed. Other languages do APIs better.
C++ is useful for implementing the guts of things. And when implementing, having your own library of shared C++ code is useful, as long as you completely control it and always compile it with your project from scratch (a library of templates, for instance). The last thing you want is to link to someone else's compiled C++ library because that's where the pain begins.
To expose a C++ implementation, use a plain C interface; the C application binary interface works very well across compilers. If you feel it absolutely necessary to expose something like the inheritance scheme of an object-oriented design, you had better do it in a language like Objective-C that can at least implement objects in a very forward-compatible way (or use tricks like exposing pointer parameters in C functions).
Both work but the c libraries have a more inconsistent object model and require multi-stage processes for the construction of their c-objects.
Once a c-based project is large enough that it needs objects in user space, not have a c++ interface is counter-productive and backporting to c++ project to c would doubly counter-productive here.
I disagree that it has anything to do with the language.
For example, I just recently started using Boost. Holy smokes, it is BEAUTIFUL, at least the parts that I've used. It just works. I've never worked with a library where I look at the example, make a guess as to how it should work, and it just works. On the other hand, the company I work at uses Boost in a completely illegible, unmaintainable way. It has nothing to do with Boost, it's whoever uses it.
To say a language such as C++ is responsible for overly complicated interfaces is just plain dumb. It's not the language it's the implementer. I can take the same $200,000 violin as Joshua Bell and make horrendous sounds from it. It doesn't mean that the violin is a piece of crap, it just means that I have no idea how to play a violin.
> To say a language such as C++ is responsible for overly complicated interfaces is just plain dumb.
I don't agree with this. C++ is a massive language that virtually nobody understand in full. As a result it makes it hard to know how to solve problems the best way, so people solve them the way that they learned to solve things which may not be how other people learned to solve things so what looks straightforward to one is a complicated mess to the other. And the worse part is, C++ is only getting bigger.
> C++ is a massive language that virtually nobody understand in full.
A professor (expert in C++) told me that there is not even a formal standard of C++.
I believe C++ developers make things so complicated because C++ itself is complicated. They only know C++ and hence this is the natural way they think. They believe that C++ can solve every problem (which is true, as for almost every other language) and some of them are so arrogant to claim that C++ is above everything else and that they don't need anything else. The latter could also be true, but the problem is: Whoever knows only a hammer as a tool sees every problem as a nail :-)
When you are a C++ developer but also know Lisp, Python etc. then you will write your C++ code in a different way that uses better paradigms. They are provably superior because C++ itself is learning from them and copying features, see C+11: http://en.wikipedia.org/wiki/C%2B%2B11
Unfortunately the horrible syntax of C++ remains. It was extended from C to C++ in such a way that it still reflects the underlying von-Neumann architecture. That makes C++ code so performant. Functional languages are not so performant because they prefer power of expression over von-Neumann compatibility. But they are catching up, see ATS in http://shootout.alioth.debian.org/u64q/which-programming-lan...
A professor (expert in C++) told me that there is not even a formal standard of C++.
To be fair, I'm not sure if this was a PL person or a systems person so I'm not sure how they're using the word, but there aren't formal[1] standards for almost every programming language.
I've met lots of people who code C++, and advocate C++.
I've met lots of people who code Java, Ruby, etc and advocate those.
I've met very few people who've shipped one product using C++ and another using Java/whatever - but those who have? They advocate using C++ sparingly if it all.
Bottom line: if you goal is to have beautiful classical music in the background for your party, then you could hire Joshua Bell, you could spend years trying to become Joshua Bell, or you could buy a fucking iPod and move on to the next problem.
I have shipped products in C++ and Java and would definitely advocate C++ over Java in almost every situation. Java manages to be more verbose and less powerful at the same time. Then again I currently use Python mostly, so I guess I am using "whatever" but I do not think Java/whatever is a valid category.
I'd argue a skilled polyglot developer probably wouldn't advocate using C/C++ until necessary, optimizing for development time. Later, as performance problems arise, it can simply be another tool to employ.
Couldn't disagree more about Boost. There are simple, elegant parts of Boost, and then there are incredibly complicated parts of Boost. Some of those complex parts also happen to be powerful (like Boost graphs). Spirit is perhaps an example of a part of Boost that doesn't justify its prolixity; Boost::Function another, and a case where C++ is really what's to blame.
When your first language you learned was C++ or Java then you tend to use the same programming strategies in other languages which probably don't fit at all.
It is not enough to know a language, you have to really KNOW it :-)
Then the title should have been "Why do bad C++ programmers make things so complicated?" Good C++ programmers can make very complicated things extremely simple.
For example, I'm working on a program where I was trying to do a mass comparison of file names between 2 directories. Essentially, I wanted to get the intersection between two sets of strings. I wondered if there was something that already did this, and after a quick google search, lo and behold, there's an STL algo that handles this, set_intersection.
So in a single line of code, I was able to solve my program that would have taken me a lot more. It wasn't complicated at all, it was extremely simple.
I'm not sure what your anecdote is really meant to prove? Doing set intersection is fairly trivial to implement, and the C++ std library comes with an implementation. The argument is that this claim doesn't scale up, which your anecdote does not address.
Well, the problem is that whenever I try to use any of this magical features it usually turns out that it either cannot do this one tiny detail which I desperately need, make me rewrite huge part of the code to do minor change or is a computational bottleneck because my use case was apparently not in use cases used to develop it.
Here's an idea: why don't we put distinctive marks at the boundaries between Top C++ and Bottom C++? Say, square brackets. If the Top C++ superficial syntax happens to be a little bit smalltalkish, we could call the resulting language Objective C!
Kidding apart, one of the anti-patterns the most specific to C++ I've met is inability to decide whether some code is high level or low level. C should have been embedded in C++, with clear segregation marks, not extended/inflated into C++.
Apple’s Mac OS X, Adobe Illustrator, Facebook, Google’s Chrome browser, the Apache MapReduce clustered data-processing architecture, Microsoft Windows 7 and Internet Explorer, Firefox, and MySQL — to name just a handful — are written in part or in their entirety with C++.
The key phrases are "to name just a handful" and "written in part."
It's very simple because most of this handful of a particular type of software are written in C, not C++.
C++ is an expert's language perfect for solving very difficult problems. Used properly it leads to an extremely elegant, efficient and agile code.
Now there's not "one way" of doing things and anybody is doing it "it's own way". There's probably more than ten C++ dialect.
When you're used to one dialect you tend to believe the other is "complicated". When you're not used to generic programming or templates you tend to think it's "over complicated" and "useless".
And if ever you mix the dialects, you end up pretty quickly with something horrible.
The endless comments on that post arguing about C-vs-C++-vs-C#-vs-ObjC make me want to jump in to a volcano. Those stories from the last few days about reducing time spent reading the internet might be on to something.
It's all bikeshedding, and thus should fail HN's test of interesting content. Junior programmers obsess about this crap. I don't know why these discussions continually pop up. It really makes HN look like /r/programming, and that isn't saying much.
Of particular note are comments in the vein of, "I haven't used Language X much, but it is terrible." I'm baffled why they're not called out for their obvious lack of intellectual honesty.
Because, all too often, those comments are not prefixed with, "I haven't used Language X, but I hear that". Instead, they masquerade as comments by someone with actual experience. The poster knows that if they were to add that disclaimer to their statement, it would lower the credibility of what they're saying.
And we all know you gotta be Right On The Internet, not intellectually honest.
I tend to think that applications developped in C++ are usually more complex and hence you get the false notion that the complexity stems from the language. While in fact you're just comparing apples to oranges.
Ignoring the Top C++ ideas, to answer the question of this article: C++ wants too much (high abstraction with high efficiency in the general domain) with too little to work with (a minimal type system, a minimal generics system). Classic work from a committee -- it's easy to agree to lofty ideals, but difficult to agree on how to get there.
So, everything built atop this doubly-pronged-mistake ends up paying the price, in complexity.
Stroustrup apparently listened to a lot of people at Bell Labs while creating c++, essentially creating it by committee. I think there is a quote by someone about this, Rob Pike? Can't find it at the moment.
C++ code isn't inherently complicated. The people who write C++ code might make their programs unnecessarily complicated. I've read at least one program that was written that way. A web application written over 12 years ago without the aid of standard libraries or common sense. However, I've also read some really great code in C++. It seems to me to be very polarizing because it lets you do what you want without forcing any conventions on you, the programmer who knows what they're doing.
I suspect the reason why people who make programs with C++ make those programs unnecessarily complex may have more to do with their psychological makeup than the limitations of the language (which I believe to be rather few).
I think he's taking a widely-accepted idea -- that the C++ language is a complicated language -- and making the unfounded leap that most C++ code is just as complicated.
For starters, the standard C++ string type is reference-counted, something that leans more toward convenience and simplicity than maximum performance. Well-designed C++ libraries that don't need to push the performance envelope are often pleasant to use.
Maybe the author wants things to be even simpler, but he's gotta provide examples. Or at least describe what his ideal Top C++ would consist of. That's the hard part. Simply saying "we should make a simple version of C++ that still lets you do low-level stuff when you want" seems vacuous.
I often hear these arguments about C++, but after having used C++ for almost my
entire career (and looking forward to use C++11) let me tell you why people
continue to use C++: because C++ is performant, and the alternatives don't give
you enough control over the internals.
Yes, C++ is incredibly complex. Inherently much more complex than, say,
Haskell; due to the fact that C++ is far less consistent, has an hairy syntax,
and non-uniform libraries (a problem which is also shared by perl and ruby, due
to the language flexibility and rhetorical "best practices").
But C++ delivers! And while not better than Python, it still allows
higher-level programming than C, while attaining the same level of performance.
While you probably don't care that python 3 (a language I also love) is overall
roughly 10% less efficient than the old 2.7, I need to figure out how to crunch
a dataset which is doubling each month, while CPU power is not increasing in
frequency any more. While your favourite language du-jour still needs to figure
out how to do basic multithreading, I'm actually exploiting it since decades,
including SSE and CUDA while I'm at it. Sure, the result is not pretty, but it
seems today that thrashing cycles "in the cloud" is not an issue anymore.
Early in C++'s existence, C++ took a lot of crap from C programmers regarding performance. In those days, C programmers complained of the "overhead" of virtual function calls... and the "bloat" of objects.. As a result, the C++ community became super focused on performance... Most of the features in C++ are completely opt in (I.E, if virtual functions are too expensive, you are free to not use them and not pay any penalty)... This attitude bled into the STL...
Another factor that complicates the STL is it's approach to generic programming... In my opinion C++ took the purists approach (instead of the pragmatic one)... So, std::list doesn't have a .sort() method... Instead, you can use the sort algorithm on any container that implements the right kind of iterators...
IMHO, C++ needs 2 standard libraries... One for purists, and one for pragmatists... (and the pragmatists will use the STL to implement their standard library, but the pragmatist::list will definitely just have a .sort() method!)...
"Whenever the C++ language designers had two competing ideas as to how they should solve some problem, they said, "OK, we'll do them both". So the language is too baroque for my taste." -- Donald E Knuth
Actually, std::list does have a sort() method, std::vector doesn't though. std::vector makes you use the other algorithms one, since it's made up of contiguous memory, while std::list isn't.
The C++ standard library for true pragmatists is Python. :) (Not technically correct, but I was making a point.) I'm pragmatic. Or try to be. So in a world where Python and C++ exist I'm going to try very hard to not touch C++ ever again. Life's too short.
There are some C++ libraries that are perhaps more complex than the equivalents in other languages, but tr1::regex's are pretty much the same as regexes in any other language, if you discount the static typing stuff.
Why do we make things so complicated? Because C++ is a low-level abomination pretending to be a high-level language. Let's start.
- Allocators in the STL are a joke, so you can't use them for implementing different memory models. So you end up implementing, e.g., linked lists from scratch.
- You can't "steal" underlying storage from a vector, which means you can't use it as a simple memory pool. So you end up implementing a vector from scratch.
- size() of containers returns an unsigned type, which is a perpetual source of code ugliness and cursing in may day-to-day programming. (The most trivial example: make a backward loop through a vector. No, don't tell me to use iterators -- index has also semantical meaning in my use-cases.)
- There is static_cast, reinterpret_cast, dynamic_cast, const_cast, but no cast which would actually check that what you're trying to do makes sense. (C and C++ standards stipulate that casting from A to B is well-defined A is a "prefix" of B. This could be extended to check for layout compatibility, so it would be legal to cast a linearly triply-derived struct with 3 floats to a float array with 3 elements. Technically, it's UB, but "it works in practice", and that "it works" could have been formalized. The compiler would be allowed to reject non-working cases.)
- Writing flexible, generic (template) code is all nice in theory, but, in practice, it goes against the idea of separate compilation, with corresponding increased compilation times.
- Debugging heavily templated code is a nightmare.
- Writing templated code is arduous. Templates being turing-complete, we can never expect as good IDE support as for Java and C#. (I toyed with some simple Java programs in NetBeans and I almost cried when I had to back to my day-job of writing C++. IntelliSense of Visual Studio, like "state of art" IDE for C++, is CRAP compared to what you get from NetBeans/Eclipse for Java.) I like more and more the idea of using a "restricted" language augmented with a powerful IDE.
- The idea of constructors establishing class invariants is all well and nice until you have to deserialize an object graph with cycles. (For this you must temporarily have an invalid object that is to be filled in later.) Constructors/destructors make sense if you actually have to manage resources.
- etc, etc.
In short, C++ has a history of incorporating half-baked features into the language and/or standard library. Things get complicated because it IS complicated to get a half-baked feature to do what you actually need to accomplish.
What half-baked features? I've already mentioned allocators and casting.
Then there's RTTI, whose implementation is complex, but I don't know whether any serious project uses it for something more than dynamic_cast. (In reality, you would like to have full reflection, but you have to implement it yourself. Yes, it's complicated.)
When you try to implement reflection yourself, you will, almost invariably, use offsetof at some point, which, technically, yields UB in most cases, but also it WORKS in the most common cases (no MI, no virtual inheritance).
There is auto, there is declspec, but there is no complete type-deduction, so you often have to spell out things twice to the compiler, even though it already has all information it needs.
There is no structural polymorphism [this is related to lacking casts] -- you're supposed to write template code with corresponding bloat of object code and other, above-mentioned downsides of templates.
iostreams are barely useful: when opening a stream fails, there is no standard-defined way of finding out WHY it failed (non-existent file, permission denied, etc.. This is a KEY feature for any program that interacts with a human user.) In C, IO functions set the errno variable, but not C++ streams. If you're lucky, errno will be set by the underlying system call and won't be changed further, but you can't count on that. Yet another fail (i.e., half-baked feature).
Another iostreams misfeature: overloaded operators << and >> : the program code leads you to believe that things execute sequentially, where, in fact, the code is converted to function calls with unspecified evaluation order of arguments. So if f() has side-effects, you don't know what cout << f() << f() will output.
==
Maybe the new C++ standard fixes some of the above issues, but I'm so disappointed by the overviews of what's new, that I won't even bother checking. Many things are just cosmetic, some are outright stupid (e.g., user-defined literals), but fundamental problems of piled-up, half-baked features remain.
I don't know C++, but I know a few other langs and can figure stuff out quickly. I was reading through Chromium sources yesterday trying to track down the conditions that must be met for a specific error to be thrown. It was all callback spaghetti and I gave up after tracing calls back through 10+ files.
As installing C++ libraries is a pain, and testing them on a large range of compilers is a pain too, the only library of note anyone ever uses is boost.
Most of Boost is a horrible templated mess. I consider myself fairly expert on C++, have contributed code to both clang and g++, and worked on C++11. I still don't use boost, and am slowly taking it out of projects which it has infected.
Boost is interesting as seeing how the bounds of C++ can be pushed, but as a library for day-to-day use it is horrible. It is just about usable if you limit yourself to a few small pieces. For example at first glance boost::spirit is a really nice parser library. However the error messages give me shivers to this day and a parser I wrote using it takes over 1GB of memory and 40 seconds to compile.