C++ confessions of a C developer
undo.io
undo.io
People mess up when coding in C++, not the other way round. Blaming the tools and seeking restrictions to one's freedom of expression in using them, won't have my sympathy. At one shop I once worked, they have added all sorts of protections to all tools they used, in order to prevent developers from doing wrong. For example, in the source configuration management, they disabled branching and parallel development, someone considered that too complicated. And Make and its language was completely hidden, of course.
I personally greatly value C++ for the expressive power it gives, compared to straight C -- not only solve the problem but express the algorithm the way that a random person reading the code instantly grasps the idea, etc. I find it harder to achieve using straight C, on average. (I am in low-level and performance-related development, choice is frequently only between the two.)
C++ is lovely when you are working alone, but as the team grows people start having very conflicting opinions on what parts of the language should be used. The only way to handle this is to restrict everyone to a subset of C++ and forbid everything else, otherwise it will be impossible to navigate the codebase.
Innocent looking things like try/catch becomes a nightmare when not everyone has taken that into consideration when they clean up resources. Either you have to enforce proper cleanup around every function call or you have to forbid everyone from throwing exceptions, there is no middle ground here.
What about style guides to forbid people to use generators, metaclasses or other advanced features?
Just to pick a language most people think it is easier than C++.
However, when I wrote generators I was actually thinking about comprehensions.
> Also because they changed a bit, in the early days you could not pass arguments back to them.
Sending arguments is certainly advanced.
So my memory is that C++ is mildly "worse is better" than was C.
I think the failure rate went up because:
- more people got drawn into programming. Some of them
got dumped there; many people with whom I worked were
actually, say aeronautical engineers who were told
that they had to pivot to software to keep their job.
- people think of the radius of software when thinking
of size, when the real measure is the surface area
of a sphere implied by that radius.
- Us old guys got to learn C on smaller projects which
were actually deployed, so we had the benefit of
going through the full cycle, playing all the roles.
- You can easily run into ctors/dtors problems when
decomposing C++ projects into modules.
- Preventing integration problems between modules just
requires a lot of good interpersonal communications
and sometimes, C++ gets in the way.
and most insidious of all
- New language systems are devilishly hard to embrace in
environments which are intended to be competitive.
One thing I've learned is - expressive power is 100% about
what the expectations of the reader are. I find differential equations more expressive than English for those things where they are appropriate, but that's because I trod a different path. With C++, the expressive power is because the reader unpacks more information per symbol-suite. Just realize that that is not the same for everyone.Then reducing the expressive power of a language will only make this worse, not better. To make code to speak to a random reader, one needs to carefully select idioms (from the professional domain, not the language's ones) he/she wants to implements, and if new/unfamiliar ones need to be introduced, make sure they are clean and easy to grasp. Removing features from the language makes this harder.
Funnily enough, people frequently illustrate their point with exceptions, whereas in my league of a single feature contributing the most to code readability, exceptions compete for #1 position! They are easy to grasp __conceptually__ for a C programmer, and when properly used, make code much easier to read. They also create a feeling of confidence when dealing with code (as they cannot be missed, contrary to error signalling via return values). Etc.
IMO, exceptions ( and I use them A Heck Of A Lot, probably to a fault in coding Tcl ) are really just a convoluted control flow mechanism, and I'd rather see the expectations made explicit than exceptional.
Heck, with full-on MMU memory protection, all code that's not in kernel mode should be free of memory overwrites anwyay. I say that as a maintainer.
The proper answer is to not shoot yourself in the foot.
Most of my programming is done in Python, where people cry about the inability to create private class members. IMO, use the convention of prefixing the name with an underscore, and if a user of your module messes with something marked as private, then it's on them when their program explodes. Don't blame Python for not supporting private members.
That's a bit like shrugging your shoulders when an infant walks off a precipice and saying the proper solution is for the infant not to walk off the precipice. The proper solution is actually either to put up a rail, making the precipice safer, or keep the infant away from the precipice in the first place (or rather, to hold the infant by the hand whilst showing them the safe way to approach the edge so that they can go solo when ready).
Or to stick with the poor analogy, the reason people used to shoot themselves in the foot is because old guns could easily go off in the holster. The answer was the safety catch.
Back on topic, the only reason this is not done with C++ is because people have come to rely on the ability to shoot from the holster (often avoiding their foot) and the back compatibility dictates that the ability must therefore remain. There are plenty of features that would not have been designed that way if C++14 had come into the world fully formed without evolving via its ancestors. It's also one reason people keep inventing new languages - it's an opportunity to learn from past mistakes with a clean slate, not just to bring in concepts that simply don't fit.
I would argue that there is scope for picking a 'major version' of C++ and consciously breaking back-compatibility in some areas. It's controversial, because it would require a lot of work in some existing C++ projects if they wish to benefit from any new language features beyond that point. Just look at the hooha made around Python 3.
It's continually updated (pdf available on GitHub).
That is not to say I fundamentally dislike the language itself; C++ certainly has some very useful features compared to C, and I sometimes wish I could make use of them when I'm writing C too, but the "C++ is so much better than C, C is bad" and all-too-common "some bloke named Herb says X, so you must follow him" sort of attitude coming from self-proclaimed "Modern C++" developers I find rather distasteful. I don't care about purity or adherence to any perceived "expert opinion", I just want to use the features of the language that make it easier and simpler to solve a problem more efficiently (and likewise, not use the features which don't help)... which is what programming languages ultimately are for, and yet I feel much of the "Modern C++" community has missed that point.
Almost all of the recent innovations in C++ focus on aspects other than OOP. I can't think very many new OOP languages features in C++11/14/17, except for `override` and `final`. All the new hotness is about functional programming and in some sense, generic programming, which, IMO, is the right direction.
> I just want to use the features of the language that make it easier and simpler to solve a problem more efficiently (and likewise, not use the features which don't help)... which is what programming languages ultimately are for, and yet I feel much of the "Modern C++" community has missed that point.
I feel that you are exactly the kind of person the author has described in the beginning of the article: a C++ sceptic, especially a "Modern C++" sceptic. The article even quotes Douglas Crockford in saying "some of those features were more trouble than they were worth." The article also quotes Bjarne Stroustrup saying "[w]ithin C++, there is a smaller and cleaner language struggling to get out." The "Modern C++" community that you have derided is exactly the community that knows not every feature in C++ is good, a community that tries to enshrine the tried and tested "good features" into "expert opinion" and "best practices" and then spread them through talks like this one.
Of course, you also need std::sort because it is much faster than qsort. Thinking about it, you probably want to implement all time-critical algorithms with generics.
... never mind, this quickly becomes a stone soup.
For things I would like to use C for std::vector is not very useful either as it grows in some generic way which is likely wasteful for specific applications. If an algorithm is time-critical then almost by definition the generic implementation in standard library won't be enough.
Try that with ICC instead, for example.
That's an impressive feat as quick sort is not tail recursive.
Edit: you can of course maintain the recursion stack by hand but that's not normally a win.
I don't think std::sort would be slower than a macro based liberty.
Obviously, you can reserve memory in a vector in the beginning.
> If an algorithm is time-critical then almost by definition the generic implementation in standard library won't be enough.
I'm afraid I'll have to disagree here as well.
Visual Studio 2015: 1.79x
WSL gcc 6.2: 1.92x
WSL clang 3.8.0: 2.08x
Ubuntu 16.10 VM gcc 6.2: 1.68x
Ubuntu 16.10 VM clang 3.8.1: 1.89x
All in favor of std::sort.
The main use case of a code base that is of value to me is: Can someone quickly determine that they have added a defect when doing maintenance? There are a lot of solutions to that.
Could you describe what/who you think this is, because I don't really see a community as a whole for who the points you make are valid. Sure there's some crazy purists everywhere, but in general I don't think it's as bad as you mention. Then again might be biased because I write way more C++ than C.
I know cherry-picking isn't usually the most fair way to discuss, but some things really strike me as unfair so I can't help it.
obsessed with "best practices"
not sure if this is really bad? When pushed to the extreme it is, but that goes for anything extreme, but I don't really feel the C++ community is like that. At least I've read many times things in the spirit of 'best practices ar good, follwing them blindly is not'. Also anecdotally from all colleagues I ever worked with those with some healthy amount of (as in, still being able to think critically) obsession with trying to Do The Right Thing, always wrote better code than those who lacked that
OOP was oversold
it surely was, but imo one of the things modern C++ teaches us is exactly that: OOP overuse is bad and you don't have to use OOP at all in C++ if you don't want to
"some bloke named Herb says X, so you must follow him" sort of attitude
which is in turn countered by an attitude of questioning everything, no matter if the author is called Herb or Bjarne, and I don't think I'm dreaming that (at least I've seen many questions on SO starting with 'in xxx Herb says y, wtf is that all bout?'). On the other hand, those guys' advice is usually golden so following it won't hurt much, as long as you don't blindly follow it.
I just want to use the features of the language that make it easier and simpler to solve a problem more efficiently
in the end everybody wants this I guess, and I do have the feeling that when searching for questions to typical problems I usually do get an answer telling me exactly what to use. Also take https://github.com/isocpp/CppCoreGuidelines for example: sure theres a whole lot of info but it does give a ton of good/bad examples, plus reasoning behind it
Go back to the mid '90s. There was a lot of awful C++ code out there, and a large part of that was the language was dangerous for many people who didn't know how to use it safely or effectively. Some of that legacy persists today in old codebases. The STL and the C++98 standard library improved upon that, but there were still dozens of interesting ways of shooting yourself in the foot.
Then we had people like Meyers who wrote whole books explaining the dangers and how to write code to avoid them. These were the best practices, and they are hugely helpful. I use them daily in my work, preventing subtle bugs, leaks, undefined behaviour and random crashes. These turned C++ into something which could be safe and effective to work with, provided you didn't ignore the guidelines and do anything irresponsible. C++11 and 14 improved upon them, and again Meyers brought out a book to explain a whole new set of pitfalls to be aware of.
I agree with your assertion that it's to some extent dogmatic. But I don't agree that it's cargo culting (though some people may be). Over the last 25 years, we've collectively learned that there are some things which you must never do (e.g. throwing exceptions in destructors), and some things which you should or must do. Like the rule of three or rule of five, RAII, or how to implement Java-style interfaces with virtual inheritance and various deleted operators and constructors. Experience has shown that this is the right way to do things, and you'd better have a good reason for deviating from that. Languages like Java benefit from being restrictive and only have one safe way to do things. That's not true for C++, where you can do what you like even if it will explode in your face, and having a set of best practices we all agree on make things safe and efficient to work with. Certainly on the team I work on, being able to refer people to Meyers and say: "this is the way to do it, and this is the rationale" is beneficial and educational to the people concerned, as they get a better understanding and appreciation of the language subtleties, rather than their work making the code segfault or do the wrong thing.
For the minority who are cargo culting, I'd rather that they followed the dogma and wrote safe code even if they don't fully understand the rationale for doing so, when the alternative is writing unsafe and buggy code. Unfortunately, there is a minority that doesn't care to understand, and for them a set of strictly enforced rules is required to avoid disaster.
I then didn't touch it for most of a decade and re-learned C++ essentially from scratch maybe...three years ago? C++11 from the jump, with slowly adding in C++14 stuff as it made sense. And I like it well enough. It's never going to be my first-choice language, but that's because the environment around C++ is a mess rather than the language itself. The language has stuff to know, but I find that most of it is stuff that is totally comprehensible given that I understand how C++ works--like, of course I need virtual dtors if I've got classes, because I understand vtables and the necessity for a lookup in one. It's complex, in that there are a ton of different components, but I don't find it complicated, in that any component is difficult to understand in isolation or that the interactions between components have particularly many gremlins. (Which isn't to say that I'm a particularly good C++ developer or anything, but I understand the land mines and how to be better at stuff I need to be better at when it matters.)
These days, I can't envision ever writing more C than is necessary to wrap C++ for use as a library in Ruby, etc.; life's too short to not have dtors.
Sadly they seem to have added more than they cleaned up. It's 2016, I don't want to have to write another freaking include guard. That should have ended years ago. UTF8 and friends are also a consistent pain, mostly because 3rd party library support is absent. This requires a strong message from the standard library or it's always going to falter.
I'd say there are bigger issues with C++ than include guards, but include guards should be out when modules are in the standard (2020?).
We used to call it C+. It was code that could not be compiled by a C compiler. But at the same time was painfully obvious that it was not proper C++.
What makes C++ proper is pretty much what code style and best practices your organization/project decides to adopt. For some projects, 'proper' may be object-oriented C++ with class hierarchies, whereas for others it may be a more functional or template-based style that doesn't even use classes a lot.
Even C++ARM was already a pleasure compared with bare bones C.
I has lucky to be able to bring Turbo Pascal best practices into C++, and that Borland had very good tooling.
On my opinion, Turbo Vision, Object Windows Library and Visual Control Library beat anything that I saw from other vendors in regard to C++ frameworks for desktop applications.
It always saddened me that given C++'s compatibility with C, most vendors would just throw over the fence a C library and it was up to us to create sane C++ bindings for them.
A good example is the sad state of database bindings.
But I do use C++ on personal projects, and C++11/14 has been quite rejuvenating and I do enjoy using it, specially for hobby coding across mobile OSes, given that it enjoy first class support in all SDKs.
Edit [0] Not saying I was any better :o)
At the heart of the languages, in C you can only make additions and divisions, in C++ you can also make classes and use containers to store data.
These are very low level building blocks, it's not enough to make any meaningful development productively. You're forced to use 3rd party libraries if you want to achieve anything. (e.g. File, IO, Socket, Thread...)
There are many libraries/frameworks out there. I'd dare saying that C and C++ are extremely fragmented [by domains, platforms, tools...].
There is a minor subset of the desktop C++ developers that pick Boost as the default library choice and try to put that in all projects. They're just very vocal on StackOverflow [and all over the internet].
Boost is still very useful for many advanced or domain specific libraries.
Also, one big deal is Asio. Which, granted, can be had separately from Boost... but Boost offers a convenient place to get it along with other minor stuff.
Then there's the question of how up-to-date the C++ implementation you're coding against is. A lot of stuff is nominally in the standard library, and if you use the latest and greatest, you might even have it out of the box... but for many people, they're still working on, at most, C++11 compilers, and so they need an outside source for stuff like filesystem.
If you use Qt, for example, it is likely that you will also use their collections, streams etc, rather than STL and Boost.
Ditto if you use MFC (though thankfully that is largely relegated to legacy code now), wxWidgets etc.
Having said that, Boost has, to significant extent, become the prototyping sandbox for things that are then added to C++ standard proper - shared_ptr, any, variant, filesystem and many others all originated in Boost, and after many years of use and polish by the community, became (or are in the process of becoming) a part of the C++ standard library. This makes Boost more important than it used to be.
For instance what happens if you opt for the strictly "modern" just Java 8 or C++17 type approach to learning these. And then you end up as the case in the real world needing to maintain legacy code? Or you collaborate with people who have been using the language for a long time but don't use these modern approaches?
The term also implies use of RAII[1] which is a technique that was used at least a decade before C++ was an ISO standard.
The third implication is a rejection of what is often called "C+" or "C with classes" which is the treatment of C++ as C with a couple extra features. Since a lot of early C++ programmers were C programmers, and a lot of early C++ compilers were fairly mediocre in their implementation of language features[3], the 90s generated a lot of "C+" code.
Really the first two points are a subset of this one as they are two prominent examples of programming techniques that are fairly foreign to C, but are extremely valuable for writing robust C++ code.
So "Modern C++" really means "Something widely considered a good idea for about a decade" at this point.
If you work on a good legacy code base, the typical old oddness will be "Version X of MSVC/GCC/whatever had poor RVO[2], so we did this ugly thing for optimization" and (obviously) either a complete lack of using the features in C++11 or using a proto-version of them via Boost.
Even the nicest legacy code bases will have really odd things whose original motivation would require serious archaeology to divine, but that happens even with relatively static languages.
1: https://en.wikipedia.org/wiki/Resource_acquisition_is_initia...
2: https://en.wikipedia.org/wiki/Return_value_optimization
3: Believe it or not, a lot of the code from Alexandrescu's book could not compile in either MSVC or GCC when it was published. The library that accompanied the book became incorporated into many compiler vendor's conformance testing.
But you should also have a look at "Tour of C++" and "Elements of Programming".
Also Microsoft has lots of Modern C++ talks on Channel 9 done by Stephan T Lavavej.
template<class Container, class Value>
void append_unique( Container& c, const Value& v ) {
if( find(begin(c), end(c), v) == end(c) )
c.emplace_back(v);
assert( !c.empty() );
}
Sutter likes this because it's written against an (conceptual) interface "Container", not an implementation like std::vector. He brags that not a single specific type appears anywhere in the code, making it much more powerful.This is modern C++, and it is totally inaccessible to a newcomer. It hits you with templates, iterators, and several generic STL functions. Call it slightly wrong, and you'll get an avalanche of impenetrable error messages. Dig a little ("what does emplace_back do?") and you're confronted with allocator traits, placement new, perfect forwarding, etc.
Modern C++ is just not accessible to newcomers. The same thing in C (non-generically) would use concrete types and a for loop.
1: https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style...
It could be that I have experience with other statically typed languages, or that I have spent time internalizing why C++ works (from a conceptual, hands-off perspective) as opposed to just how, but...that seems reasonable to me. I probably wouldn't write it that way because I don't have code that relies on that kind of flexibility, but it's quite comprehensible. If your complaint is merely that C++ expects you to know stuff to read it...I mean...yeah, sure, it does, but if that's a problem we have deeper rot as professionals to deal with.
And it is a problem, given how much we venerate C while it keeps on being wholly unsafe at any speed. But, hey. Some of us need buffer overflows to stay employed, I guess.
On average, C++ has the highest (talk + best practices + monstrous refactoring) / (actual code) ratio of any language I know.
The C++ projects I worked on moved at a glacial pace, with constant discussions about the best object hierarchy, patches because some compiler had a different interpretation of the 100+ nested templates than some other compiler, and so on.
Oh, and the code also segfaulted and was buggy.
There are developers who assert that C++11 is a step change of a language from C++98, and that may be true, but reasonably written, stdlib-adhering C++11 and C++14 are solid, hard-to-break languages in the general case and horror stories with regards to C++98 seem curious unless one wants to bring up failures of early DHTML applications in conversations about React.
(The build environments? Often a different story.)
I am skeptic of what this sentence is supposed to mean. Well, except that you've programmed in C++ for a very short period of time, in what seems like a very particular environment.
As far as I'm concerned. C++11 and C++14 came out with no design guidelines, zero different way to organize the code, no coding guidelines, no good practises. Just like C++03 before it :D
By choice. I use C++ where I absolutely must and nowhere else. It exists for particular use cases and no others. If I need high-performance native code with a strong(-enough) type system and I can't use Rust, I will use C++. Why would I use it elsewhere?
I have been using C++ for about five years, which by some lights is a short period of time--but, then, I've also been programming for twenty and I can comfortably say that I understand the hows and whys of C++ as a language even if I don't practice it heavily. (I learned them even before I used C++ at all because they reveal the particulars of the underlying techniques used by other programming environments, such as the JVM.)
And C++11 and later is pretty easily made straightforward, comprehensible, and manageable. (I mean, if you only stuck to the axioms of "never pass a pointer" and "always use std::unique_ptr and std::shared_ptr" you'll have code that's leaps and bounds past C++98/03 or anything written in C.) I write it in such a style and the code I'm exposed to generally does, too. Sorry that you don't?
I'm not complaining. Herb Sutter has his arguments for why experts should write code this way.
But I don't think newcomers should attempt to write in this way. You can't understand iterators before you understand pointers, or templates before concrete types and functions, or emplace_back before push_back, or Concepts before classes, or auto before concrete types. You may be able to use them, but you won't understand them, and you'll be useless when things go wrong.
One last illustration: you mention your experience with statically typed languages. But C++ templates provide much less static type checking than non-template code. It is definitely not a place to start.
Looks like some people like that approach.
Guess you'll never win
- it's a simple templated function; could even be reduced to one parameter if you use "typename Container::value_type" for Value - it's using find (standard library algorithm) - it's using begin and end (iterators common to all standard library containers) - it's using emplace_back (new to C++11 with rvalue refs, but everyone already knows push_back which it replaces)
While emplace_back might be new to people using C++98, everything else is well over two decades old. It's extensively documented, and if you've ever done anything with the standard library, it's basic stuff. If any C++ programmer struggles to understand this, then they are essentially stating that they don't know the basics of the standard library or the language.
All that I'm trying to advocate are things like smart pointers instead of raw pointers and relying on iterators so you can use range-based for loops. Avoid some of the classic problems like forgetting to de-allocate things, de-allocating twice, using out-of-range indexes, and all that.
That's always going to be a problem, but that's no reason to not learn the latest versions.
> And then you end up as the case in the real world needing to maintain legacy code?
It's not like learning the newer ways of doing things prevents you from learning everything else.
Additionally keep looking for opportunities to sneak in some features, or pushing for toolchain updates.
They look like a way to add some safety to pointers and memory allocation, for those who demand bare-metal performance. Wondering if it's taking off...