Hacker News new | past | comments | ask | show | jobs | submit login
C++ Coding Guidelines (2014) (howardhinnant.github.io)
52 points by turingbook on June 4, 2017 | hide | past | favorite | 46 comments



I strongly disagree with the prioritization of compile time and run time performance over maintainability. I can't count the number of times this has bitten me because of some premature optimization someone chose to write. Most of the time the "optimized" code is faster, but it was never a bottleneck to begin with, and is significantly harder to read/modify. Also, if compile time is so important to you, try using Meson as your build system.


Compile time is a huge factor in maintainability!


It's really not. BUCK and other build systems make compile lightning fast, and when you have a complex multithreaded bug because of sloppy optimization, faster compiles won't help.


This is exactly the sort of situation where fast compiles often do help, in my view! Debugging is one case where speed of progress can depend on build time, because you need to run the code after each change to see what effect it had and to decide what to do next.


Optimised code is occasionally so cryptic that you'll just waste a few hours figuring out what it does, which basically nullifies the effect of faster compile time.


> when you have a complex multithreaded bug because of sloppy optimization, faster compiles won't help.

Code that cryptic is exactly the kind of code I often want to modify with more instrumentation, logging, sanity checks, test cases, etcetera. "The problem seems to go away if I do X" isn't a real solution, but it can be a great hint at times, especially if you've got a small and relevant resulting difference in the disassembly. "X asserts right at the site of the fundamental bug" is handing you the solution on a gold platter. Slow build times discourage adding these things, and refactoring systems to be harder to misuse. If it takes hours to rebuild your full setup, nobody but the crazy people (such as myself) will touch common headers to preemptively add such things to make the codebase more maintainable.

Even if it's not helping you in the instant, it's helping you in the long run.

> BUCK and other build systems make compile lightning fast

It sounds like you have compile times under control wherever you are. I'm happy for you - and perhaps a little envious ;).

BUCK won't magically solve multi-minute link times. Full builds of a lot of things I've worked on require zipped, compressed, and cryptographically signed packages of gigabytes (which must then be verified, uncompressed, and unpackaged to test) - BUCK et all can't solve this terribly well either. Distributed build systems are pretty magical about solving embarrassingly parallel compiles, although even for compilation it doesn't help me too much when I'm on the bleeding edge doing hours worth of builds just to test a reasonable subset of our build configurations - maxing out a 6-core - before rolling out the latest SDKs and compilers to the rest of the build farm and my coworkers.

There are various tricks one can employ to help keep build/deploy/test cycle times in check for the common case - faster linkers like gold, distributed build systems like BUCK, dev builds which stream unsigned assets from host computers instead of from a package, etc. - but someone has to do the work of setting those all up.


This doesn't address the argument that sacrificing other factors of maintainability for faster compile times can hurt you in the long run


How does meson help make builds faster? C++ makes compilation slow, a build system will only help so much.


It uses ninja as a backend which has a no-op build time of < 1 second. This means that incremental builds, especially in large codebases are much faster than make. It will also use ccache automatically if its available. It does some neat tricks like only re-linking shared libraries if one or more symbols has changed, etc... In my experience, converting to Meson has always led to a substantial reduction in build time, both incremental and full.


Ninja can help a lot, but it won't magically make the cpp file that you're actively working on which takes a minute to compile not take a minute to compile, and that's a very real possibility when you write c++ with no thought to compile times.


Cool, thanks for the info


Is compile time a real issue these days? I worked on a code base with thousands of source files and millions (in the 10s [corrected from 100s]) of lines of code 10 years ago and the build process took ~20 minutes. That's a long time, but it was 10 years ago and without a parallel build process. The code leveraged templates and other features of C++ that tend to lengthen compile times, too. Sure, we should strive for minimal compile time (it's expensive idle-time for a developer) but I'm not convinced it's worth allocating developer effort to except in egregious cases. Quality development that doesn't explicitly carve out time to focus on compile time should produce code that compiles in a reasonable time anyway.

In general (and for the majority of cases by a significant margin) I agree that maintainability should overrule run time performance. However as always there are tradeoffs to consider. If one is only going to use a program briefly or a small number of times maintainability becomes a lower priority.


Yes. For example our project needs about 15-20 mins for a full compile on the most expensive 15" tMBP. (It's about 10 minutes on a desktop machine due to excessive thermal throttling on the laptop but Apple kinda doesn't support those anymore).

For an Android build that project needs to build at least two architectures (x86 / armv7) to even function on emulator and target device.

CCache, ninja and all other stuff really helps, but we're still talking about minutes of compilation for a C++14 project.


I can say that in my personal experience, overly complex, difficult-to-follow code (point 4 in this hierarchy, subservient to compile time) seems to have been much more of a time-waster than build time, and certainly a greater source of bugs and inefficiency.

Build time is not necessarily wasted, either - it is time to think about what comes next.


> Is compile time a real issue these days? I worked on a code base with thousands of source files and millions (in the 100s) of lines of code 10 years ago and the build process took ~20 minutes. That's a long time, but it was 10 years ago and without a parallel build process.

Yes, I think it's still an issue. Let's say your project from 10 years ago took 20m to compile. Using a good build chain one could say, that's possible in 2m today, but this is still a lot of time.

Now, if it would take 2s - that would be a real improvement. One should not underestimate the vast benefits of a fast feedback loop.


Fast feedback can be useful, certainly. But consider that there is diminishing returns to be had. The difference between 2 minutes and 2 seconds may seem significant for this, but I have doubts. In any case it certainly isn't going to be as noticeable as a reduction from 20 minutes to 2 minutes.


The difference between 2 minutes and 2 seconds is the difference between me alt-tabbing and reading HN (and potentially getting distracted for much longer than the compile actually takes), and just staring at the IDE while the compile runs.


A huge difference in write-save-compile cycle time if it's 2 seconds or 2 minutes. According to my own like, the sweet spot is around 6 - 8 seconds, anything over that for compiling the whole project start to feel annoying.

Good thing you can script C++ with scripting languages like Lua to overcome this also though.


I would say the difference between 2 seconds and 2 minutes is significantly more noticeable than the step from 20 minutes to 2 minutes.

Apart from the fact that the factor is bigger (60 vs 10) it matters that if you go under 10s your workflow changes, as you don't have to work in "async"-mode anymore "oh, while this compiles I check this stuff in the documentation". For me who is not the best multi-tasker this is a real benefit.


I think it depends highly on what kind of developer you are and what your workflow is like. For some developers, frequent compiling is part of the development process. For others, it's something you do at the end (or after large chunks of work) to test/verify.

If you're one of those "Work on the next line, keep changing it until it compiles, then keep changing it until it runs" developers, then yea you'll want fast compiles.

If your workflow is such that you take your time, do your work once, and compile/run/test as the last step before you're ready to commit, then it doesn't really matter if your project takes 20 minutes to compile. You're only doing it once or twice a day.


When working on a new project I sometimes spend weeks​ coding without​ attempting to compile once (it usually takes a couple of day then to sake down all typos and thinkos); my boss is horrified by this but it works for me. In this scenario I care little about compiletime.

The problem​ is when I'm fixing bugs or adding small features to an existing codebase, especially when tests need to be added or modified. The compiletime turnarounds does kill my productivity in these cases.


It's an issue. In fact, long C++ compile times were supposedly one of the motivating factors of the Go language:

"That 2007 [Google build] took 45 minutes using a precursor distributed build system; today's version of the same program takes 27 minutes, but of course the program and its dependencies have grown in the interim ... The origin myth for Go states that it was during one of those 45 minute builds that Go was conceived."

[1] https://talks.golang.org/2012/splash.article


The project with the fastest built time I worked on was between 20-30 mins for a full build (on a dual socket desktop, significantly more on virtual machines); incremental builds were much faster, well below the minute, but touching some critical header files is always painful. This is in the order of 1 milion lines, but a lot is unfortunately in headers.

The slowest was an 8 hour overnight build for a subset of all targets. The build would also randomly fail because of race conditions in a broken build system. I don't remember the size, but probably multiple 10s of millions of lines.


Our fastest platform takes about 20 minutes to compile on a fast system (often down to about 30 seconds for incremental builds, and less if you do something fun like building in a RAM disk). On the same system, our slowest platform takes about an hour and a half (some different plugins are built, but mostly that platform just has a slow compiler). Later stages of the build have the outputs of previous stages as prerequisites.

I wouldn't put build speed above maintainability, but build speed can still be a problem for large projects.


Can you describe the build process? Building 100's of millions of lines of C++ in 20 minutes is quite a feat even today. How much parallelism and build distribution was used?


First, that's a typo. Should have been 10s not 100s. I'll correct it in the original comment.

But the result was achieved mainly by lots of compacting source code into smaller TUs, mainly, e.g. by #include-ing dozens or hundreds of .cpp files into the one that was actually sent to the compiler, and other tricks to make the number of the things the compiler had to do much smaller than a naive ("compile everything as seen in the source tree") approach would require. The original build system was not optimized in that way and took much, much longer (well over an hour, as I heard it, but that was before my time). They implemented parallel and distributed builds after I left, and as I understand it that took the whole process down to "a few minutes."

Now, it's certainly debatable whether that level of effort should be required of a build system (I lean towards "no").


None of these are unique to C++ so I recommend simplifying the title. Even compile time recommendations apply to many languages.

Also, it is strange for maintainability to be #4 when correctness is #1 because it is equally important for code to remain correct over time. There is nothing more frustrating than reopening issues over and over because people keep accidentally breaking things in unmaintainable code.


"Also, it is strange for maintainability to be #4 when correctness is #1 because it is equally important for code to remain correct over time. "

Absolutely. This was known far back as Ada which had reduction of errors in maintenance phase as one of its design goals for its syntax and semantics.


I don't think it is possible to state a general list of priorities such like this on all source code and all contexts. What is not apparent here is the context in which these priorities were stated. I could easily argue that maintainability (both reading and writing) is more important than runtime performance in any other given context.

What would be interesting is a more elaborate discussion of the priorities based on the context (which is unknown in this case).


One way of looking at the priorities is through the lens of "how difficult will it be to address these topics after the product ships?", with all of the inertia that entails.

For example, most runtime performance is fundamentally architectural. Once you ship an architecture it is nearly impossible to change it in practice. You rarely get a second chance to do this correctly.

Correctness can be particularly insidious if the code behaves well enough to use. There are many examples of incorrectness that became a "feature" after it shipped because users started exploiting the side-effects of incorrectness in their own applications, making it very difficult or impossible to properly address the underlying broken-ness. A lot of code spaghetti is the product of a janky feature implementation that has some incorrect behavior that needs to be supported indefinitely to keep users happy. (This is what I always fear most when developing software.)

Compile times are a partly a side-effect of architecture but in practice you can often make large improvements without materially altering the design of the software. With minimal thoughtfulness in the software design, you can push this off until it really becomes painful without losing the ability to change it.

And so on. Readability and writability are among the easiest things to change after the fact.


Very good point. While all of these guidelines are spot on, I'll also usually favor maintainability/readability (as in proper combination of applying standard Good Practices and design patterns) over anything else, if context allows. Then again, I noticed since C++11 and beyond there is much less of a trade-off between maintainability/readability and runtime performance than there used to be. Might also be compilers getting better, and hardware in general somewhat faster. The latter also being why I start to care less about compile-time performance.


How can you lose readibility to something else? Can't you comment what a tricky bit of code does? Full disclaimer, I'm a fairly novice programmer.


Consider the case when someone is prototyping a hardware device. They might start out with a bunch of separate modules connected by wires, with the benefit that those modules can be reconnected in different ways easily, and the connections are relatively clear. Later, they'll do the work to custom-build a circuit board, chips, and all that to make a marketable product. One problem: The finished product is much harder to modify than the prototype was.

We expect software to be more malleable. There are a lot of ways to architect software that will increase efficiency in some way but will make it harder to modify the software in the future (adding new features, closing security holes, etc). On top of that, code that's tightly tied together becomes harder to read.

Comments are great, but they've got to be maintained too, except that you don't have customers or compilers/parsers/etc enforcing that. So if a bunch of code is complicated, has a lot of inter-relationships between different pieces, etc, then part of the job is making sure that the comment is up-to-date and still correctly describes the purpose and use of the code. Clever, hard-to-read bits of code should be as small and far apart from each other as possible.

Maintainability includes more aspects than just readability.


That's a great answer thank you for your response, do you think it would be possible to write a plugin that auto comments code and updates the comments for particularly tricky parts say something that comments [n] then tacks on what n does at the end of the code. Then in the background keep a running list of what got referenced where and updates those references as needed?


The rule of thumb is that comments should explain the "why", and the code itself should explain the "how", so I tend to be skeptical of documenting how something works in the comments.

Say that we've got that 1% case that's irreducibly complex (i.e. clarifying/simplifying the code kills the performance of something that gets run a million times a second), where we might want to explain the "how" because the code's unclear but can't be changed. The comment itself might take the form of pseudocode representing the slower-but-clearer version of the code. Being a complex and sensitive area, you'd want to have as many tests built around it as possible, to verify that the behavior doesn't change unless it was planned in advance. Part of the code-review would be that any change in behavior (as reflected by changes to the tests) would also need to be documented in the comments.

Computers are good at accurately tracking multitudes of small details (like automatically tracking all the places a piece of code is called, etc). Getting them to synthesize a summary of something's behavior ("seeing the forest instead of just the trees", the kind of thing that would be useful as a code comment) sounds like it would be interesting to research, but I don't think there's anything like that right now.


I'm hardly a guru, but I think that a tricky code, with an explanation. invites a number of subtle problems. You end up having to prove that the code and its explanation actually agree with one another. For instance your explanation could be crystal clear, but the code could still contain a bug, that's harder to see because it's tricky. Or, someone could update the code and not the explanation.


Those priorities are self-contradictory.

If you prioritize performance over maintainability, you're sacrificing long-term correctness, which is supposed to be your #1 priority.

PS. Also, "C++ Coding Guidelines"? This is neither coding guidelines, nor anything specific to C++.


"Make sure that what you assume won't compile actually doesn't compile."

How do I do that properly?

How do I test for undefined behaviour? I mean when I have code where I know that (ab-)using it in a certain way will trigger undefined behaviour, how do I test that?


If you want to test that something doesn't compile, you just try to compile it and it either will succeed or fall. UB is a runtime thing, so that's not relevant to this point.


You mean including code snippets of not compiling code in the automated(!) tests and compiling them from there?

Hm, That makes sense. Anybody know a good framework for this? I can imagine that supporting different compilers output isn't a trivial thing that one should have to rebuild themselves.

Btw: The undefined behaviour question was meant to be unrelated to the "not compiling" one.


Offhand, I would just use whatever CI tool you're already using to test various compiler/OS combinations and then just write a bash script to run that compiles a test file (given as an argument) and exits with the opposite exit code that the compiler gives (i.e. 0 for failure, 1 for success).


Almost forgot to respond to the UB part: you can use clang's UBSAN to help detect when undefined behavior occurs.

https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html


Many test systems allow for expected-fail tests.

Also this being c++, of course there are ways to test that an expression​ does not compile via metaprogramming (i.e. SFINAE).


I think that expressing these important points as priorities is missing a better way of looking at them.

I see them as tensions, the engineer's job and wisdom is in balancing these tensions

Except correctness. Code should be correct.


These coding guidelines might be a good idea overall but they do not seem very specific to C++. You could take the same guidelines and apply them to C, Ada, Obj-C, Pascal, etc.

If you look for C++ specific guidelines I suggest this: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC...

I am saying this since C++ gives you a lot of power, but also a lot of responsibility and room for error making a more specific guide a very desirable thing.

Then, you don't use INT_MAX anymore, you use std::numeric_limits<int>::max()


Except in very special cases, I would suggest Maintainability be #2.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: