Hello and welcome to C++ Weekly. I am your host Jason Turner. I'm available for code reviews and
on-site training. In this episode I want to talk to you about how to write C++ the right way. Yeah,
might be a little bit of a click bait title, but I have some points that I'd very much like to make.
[Music]
Now the main key point and the subtitle to this episode needs to be
"We are currently in a golden age of C++ tooling." Let's go ahead and start with a disclaimer here.
There are other programming languages that basically have many of these things built in.
We do not. We have to go out of our way to enable many of these things. Now,
do trust me that this is going to be a rapid fire episode with lots of information coming at you,
and you might have to review it. You might need to go check out this video description for a lot
of these links and information that I'm going to be discussing, as well as, of course, you know,
buy a T-shirt and a Best Practices book while you're down there. So in C++ we have the tools
available. Yeah, we don't have these things built into our language, but like The Six Million Dollar
Man, we do have the technology. We have the capability. We can build this language into
what we want it to be. So the first thing that you absolutely need when writing C++ the right way
is a continuous build environment. This is basically non-negotiable for modern
development. It's impossible to stay up on all of your warnings and errors and
whatever to make sure that you are, you know, doing the right thing with your
code. There's plenty of free options out there - there's GitHub, GitLab, Jenkins,
etc. I don't know, there's there's really a ton of options here, so I'm just going to ask,
"What's your favorite?" I think you need to just go ahead and comment and just let me know, like,
what continuous build environment did I leave out that you absolutely love here. You probably have
one already if you don't use one of these that are available. Now the next thing is maybe slightly
contentious for some people. You need to use as many compilers as you can. Obviously there's GCC,
there's Clang and there's Visual Studio - let's just refer to that as cl.exe. Perhaps less obvious
is clang-cl, which emulates the Microsoft compiler and runs on Windows. It can be a
drop in replacement for cl.exe. Why do we need as many compilers as we possibly can? Well, because
different compilers find different warnings - this is critical to making sure that you
are writing standards compliant code. You need some sort of organized testing framework. Now
I know I'm going to miss your favorite one here, so you absolutely have to comment on
this one, but we've got doctest, which claims to be the fastest
test framework for C++ compile time testing framework. Absolutely check that out,
and then we have Catch2, which has been going through a lot of reworks lately. You know, yeah,
I have my browser in dark mode and it's kind of funny to me what the logo looks like in this case
so I'm just going to leave it like that, but Catch2 - also on GitHub - free, open source,
easy to use. Then we have GTest, which is one from Google, and we have Boost.Test,
and of course we have your favorite that you need to comment on and make sure that we are, you know,
covering all of the options here - the key is that it has to be something that's easy
to use. Now that you have tests you need test coverage analysis.
There's a few options here. I want one that's going to show up in my dashboard
that my continuous build environment has, so in an ideal world I have my continuous build environment
posting continuous test coverage analysis. This can be moderately difficult to set up, but there
are some tools that can help you with this. First of all I want to mention Coveralls, which I have
used in the past, and this gives you a historical timeline, code coverage analysis, integrates with
GitHub without any difficulty; and then I want to mention Codecov, which again, you know, these
things are largely free for open source projects and you can choose to pay for them if you have a
closed source project, and then probably something else that I'm missing that is your favorite.
Of course leave a comment and tell me if you've got some sort of test coverage analysis
and reporting that you want to mention. Now it is definitely critical that you have some
sort of historical tracking because we really need to know if we're regressing here. Now the
next part is where I know I'm going to annoy many of you, but we're going to do it anyhow.
You need as much static analysis as possible here. This is going to come in the form of at least
-Wall, -Wextra, -Wshadow, -Wconversion, -Wpedantic and -Werror if you're on GCC/Clang family of
compilers, and if you are on Visual Studio then we want -W4 and -Werror, which is called WX on
cl.exe. Okay. Now I consider this to be a minimum. If you don't treat your warnings as errors
then you are not going to take your warnings seriously - you need to take your warning
seriously, and I know (because I've had this argument with all of you at some point on
Twitter) that most of you don't like this one of -Werror, and most of you don't like
-Wconversion, but hopefully you watched my episode on the PlayStation jailbreak that never
should have happened that was trivially catchable with -Wconversion and you're willing to take it
seriously today. Okay, so that's bare minimum. That's the compiler. Now GCC has a relatively
recent analyzer option which will have links in the video description, but you can see it
is attempting to build its own static analyzer straight into the compiler. Now to be clear,
static analyzers are things that execute by analyzing the source code, they are not runtime
checks. Then of course we have Visual Studio's static analyzer that's built into its compiler.
I say of course, but very few of you actually use this (I know from training), and then we've
got cppcheck, which is a free and open source static analysis tool as well that has built-in
integration in CMake, and we have clang-tidy, which also has built-in integration with CMake,
and there are other tools that are free for open source use such as PVS-Studio,
and we've got a whole wide range of tools from Sonarsource - SonarQube, SonarLint, SonarCloud,
all these things that can integrate with various levels with your tools, and there's of course
your personal favorite that, you know, I want you to comment on. I know that there's a pretty
good chance that a lot of you are going to comment on static analysis tools that are written for C,
not for C++, and they don't actually do a very good job of analyzing C++. Go ahead and
leave them as comments. They probably don't work very well with, you know, something like C++20,
but you might still be using them, I don't know. Okay, as much static analysis as possible. Now
the next thing that you need is runtime analysis during testing. Hopefully you are familiar with
the sanitizers - undefined behavior, address, memory, thread - these things all do a runtime
analysis of your code with built-in compile time instrumentation. GCC, Clang and Visual Studio
all have varying levels of support for these tools today, and they are absolutely amazing,
and they must be enabled during testing otherwise you're not going to catch your memory errors on
your test, which brings us full circle as to why we need code coverage analysis - because we
need to make sure that we're actually testing the things that we think that we're testing
so that we are getting this runtime analysis of the code that we're testing that we think
that we should be testing. It's all a good idea. Now the next couple of things that I
need to mention are considerably slower than the sanitizers, but they work when
you cannot compile your code specifically to use the sanitizers, and that is Dr. Memory,
which I find is a tool that very few people know about. It is considerably faster in many
cases than Valgrind and it has the advantage of actually working on Windows, which is quite
the thing because Valgrind does not, which, of course, means that I need to mention Valgrind,
which can do many of the same things that we can get from our sanitizers.
It kind of looks like this: Most of the things that the sanitizers can
do Valgrind and DrMemory can do, unfortunately at something like
20 times the additional runtime cost, so not necessarily an ideal situation here, but they are
tools that are available. Then you should also be aware that your compiler implementation has
debug checked iterators. Now I'm not going to put links to all this right here in this conversation,
but there will be links in that video description I keep telling you to go to, and you can look at
the lib standard C++ and the Visual Studio checked iterators links. Now the next one is
one that I'm going to spend a lot more time on in a future episode, but it is fuzz testing. Now the
idea of fuzz testing is that there's a tool that generates random and novel inputs to your API.
You need this because it's going to use your API in a way that you would never think of using
your API, and this is a good thing, because you use your API, correctly it does not. So
it's going to find all of your memory errors and undefined behavior in your code. Fuzz testers are
extraordinarily good at this. So it is key here that when your fuzz tester is running
you have all of your runtime analysis enabled just like you do in the case of using runtime
analysis during your regular design unit test; and that brings us to the final thing here -
ship with hardening enabled. I don't think I have had a single class or student who has ever said
that they do this, so I'm going to do a relatively high level overview. We have Control Flow Guard
from Microsoft. If you are using Visual Studio, Microsoft recommends to always deploy with this
because it helps prevent people from forcing your binary into an undefined behavior situation,
effectively, and then we have FORTIFY_SOURCE, and this is used by GCC and I think Clank. It's
disturbingly difficult to get specifics about it, but it basically is recommended for shipping
your binary with FORTIFY_SOURCE enabled, and it has similar overlap to something like Control
Flow Guard. Your compiler probably has a stack protector option that you can deploy with as well,
and then this one is worth noting. I'm going to do this like this -
you don't want to deploy with address sanitizer because this actually
it widens the attack surface area on your binary, but undefined behavior sanitizer actually has
this feature called "Minimal Runtime," and you have to set up some specific options.
Again, there's links to this in the video description.
Well, there's a link in the video description that will take you to all of this, but it has a
small attack surface library that you can use to effectively outlaw certain types of undefined
behavior in your program; and if you're going to use this then you want to be aware of these
that determine if your program is going to print a verbose error and report and continue, which is
the default, or print the error and then exit the program in the case of hitting undefined behavior.
You should definitely choose wisely what you use here in your deployed binary.
Okay, uh, so there you go. Now the last thing that I'm going to share with you is that a lot of these
options already exist in my "Use the Tools" section of my Cpp Best Practices repository
which is a portable coding standards document that you can use in your project for your team,
whatever. I'll have a link to that also in this information that you'll be getting in the notes
for this episode, and a lot of these things are set up also in my Best Practices CMake
Template Starter Project. So be sure you check all those things out, and, again, comment because
I know many of you have favorites here that I overlooked. Let me know what I missed, and
thanks for watching this episode of C++ Weekly. Be sure to subscribe and give this video a thumbs up.