Disclaimer: these arenât new protipz. I didnât make them up. Theyâre actually straight out of the Chromium code style, theyâre pretty trivial, and you might already use them. But just in case youâre not a Chromium committer (the outrage), or are fairly new at C++ and want to make your code less suck, here they are. I think theyâre neat.
Copy constructors and their brethren
You know that scene from The Fly when Jeff Goldblum, having not screwed up teleporting a small baboon, decides he should totally teleport himself? But then he screws that up (because software), manages to turn himself into a giant terrifying fly (because David Cronenberg), and continues to give me nightmares as an adult.
Thatâs exactly how I feel about copy constructors. You can absolutely get them right, but theyâre a pain, and among other crimes theyâre committing, theyâre sometimes deceivingly slow. The point is, most of the time you donât even need them. I mean, Jeff Goldblum teleported himself like three meters away. Couldnât he have just walked?
What we tend to do instead is convince the compiler to get annoyed with us if we try to use a copy constructor. This is easy because the compiler <3s being annoyed with us. So we can define a nice macro (stay with me) that adds a private declaration, but doesnât implement it:
Which you would then use in your private section of your class, like so:
Now, when you try to be ambitious and clone Jeff Goldblum,
Clang will tell you something like âerror: calling a private constructor of class âHoomanââ. Other compilers might tell you other things, but theyâll generally have the same annoyed tone. Now would be a good time to apologize to your compiler for all the silly things youâve done in the past.
Digression on macros
My C++ motto is âYes, but just donâtâ. Yes, macros are weird and evil and if you use them incorrectly you will open the hellmouth. So try not to. We will use macros in a civilized way in here, and if youâre writing something like #define TRUE FALSE
we will all agree that it was an uncivilized thing to do and itâs your turn to tell Buffy about the hellmouth.
Debug checks
The Chromium code is peppered with these things we call DCHECKs. Theyâre asserts that run only in debug builds, so that you will catch bad scenarios in development and testing but you wonât give the user a panic attack in production. Ideally, we all have 100% test coverage that we run in debug mode (because itâs obviously sooper fast), so we detect all of the herp derps and nothing ever goes wrong in production. Ideally.
My favourite usage of dchecks is to make sure that Iâm not accidentally breaking code by adding new values to an enum, and have them be unintentionally handled by catch-all blocks.
Letâs pretend we have this enum:
Which we would use for dressing up in the morning like so:
This way, if later on someone adds PANTS
to THINGS_TO_WEAR
and ends up calling this code with thing = PANTS
, the runtime will meep and I wonât have accidentally put my pants on my head. See what I did there? (If you were bothered that the enum wasnât sorted alphabetically, now you know why. Letâs move on.)
You can also do this with a similar NOTREACHED() assert, to make sure your new values are not caught by a default switch case.
Unnamed namespaces
This is an unnamed namespace, which I am declaring in a .cc
file, and it is the coolest:
Reasons why itâs the coolest are:
- This function is available only inside this
.cc
file so it doesnât make your class obese. - You donât have to remember which of the 3+ meanings of
static
youâre referring to when defining a file scoped static variable. This means youâre playing by the âYes, but donâtâ rules of having fun with C++, which donât give you headaches. - If you care about this sort of thing, your function name gets a nicer mangled name.
Itâs basically just that second bullet though.
Forward declarations >> #includes
This is probaby the most boring of all the topics, but the most useful one. Having a header file include everything but the kitchen sink is a little unfortunate. Your compiler is unhappy because it needs to open all those files, which in turn will make you unhappy, because every time you touch a header file, it will trigger seventy billion other files to feel like they need to be recompiled. Ainât nobody got time for that.
Instead, what we can do is forward declare the class (class Foo;
) in the .h
file, and include it (#include "Foo.h";
) in the .cc
file. That basically means youâre promising the compiler this type exists, and that you will tell it what the type looks like when it (the compiler) needs it. If the compiler needs to use the type and you havenât included it, I promise you it will meep.
But because this is C++, the rules of this game are a little tricky, and will sometimes get you into an argument with the compiler. The question I try to answer is âDoes the compiler need to know the size or contents of the class Foo
?â
- If the answer is yes, and the compiler cares (e.g. inheriting from/making a member of that incomplete type), then you wonât be able to forward declare it. You have to do the promising and the explaining in the same place, so might as well just include the file.
- If the answer is no, and the compiler doesnât care (e.g. youâre declaring but not defining functions that use the incomplete type), then forward declare it away!
This means you have to be a little careful when including the type in the .cc
file. If there are two types that are called the exact same thing and you include the wrong header, youâll have a bad time. So, you know, just donât. :)
Thatâs it, thatâs all.
Go forth and write nicer C++ code.