Revision 4.2
Tim PenheyEach style point has a summary for which additional information is available by toggling the accompanying arrow button that looks this way:
. You may toggle all summaries with the big arrow button:As every C++ programmer knows, the language has many powerful features, but this power brings with it complexity, which in turn can make code more bug-prone and harder to read and maintain.
The goal of this guide is to manage this complexity by describing in detail the dos and don'ts of writing C++ code. These rules exist to keep the code base manageable while still allowing coders to use C++ language features productively.
Style, also known as readability, is what we call the conventions that govern our C++ code. The term Style is a bit of a misnomer, since these conventions cover far more than just source file formatting.
One way in which we keep the code base manageable is by enforcing consistency. It is very important that any programmer be able to look at another's code and quickly understand it. Maintaining a uniform style and following conventions means that we can more easily use "pattern-matching" to infer what various symbols are and what invariants are true about them. Creating common, required idioms and patterns makes code much easier to understand. In some cases there might be good arguments for changing certain style rules, but we nonetheless keep things as they are in order to preserve consistency.
Another issue this guide addresses is that of C++ feature bloat. C++ is a huge language with many advanced features. In some cases we constrain, or even ban, use of certain features. We do this to keep code simple and to avoid the various common errors and problems that these features can cause. This guide lists these features and explains why their use is restricted.
Note that this guide is not a C++ tutorial: we assume that the reader is familiar with the language.
In general, every .cpp
file should have an associated
.h
file. There are some common exceptions, such as
unit tests and small .cpp
files containing just a
main()
function.
Correct use of header files can make a huge difference to the readability, size and performance of your code.
The following rules will guide you through the various pitfalls of using header files.
#define
guards to
prevent multiple inclusion. The format of the symbol name
should be
<PROJECT>_<PATH>_<FILE>_H_
.
-inl.h
suffix to define
complex inline functions when needed.
.h
, your
project's private
.h
, other libraries' .h
, .C library, C++ library,
.cpp
files are encouraged. With
named namespaces, choose the name based on the
project, and possibly its path.
Do not use a using-directive in a header file.
= delete;
.
struct
only for passive objects that carry data;
everything else is a class
.
public
.
private
, and provide
access to them through accessor functions as needed (for
technical reasons, we allow data members of a test fixture class
to be protected
when using
Google Test). Typically a variable would be
called foo
and the accessor function
get_foo()
. You may also want a mutator function
set_foo()
.
Exception: static const
data members need not
be private
.
public:
before private:
, methods
before data members (variables), etc.
static_cast<>()
. Do not use
other cast formats like int y = (int)x;
or
int y = int(x);
.
Definition:
Streams are a replacement for printf()
and
scanf()
.
Pros:
With streams, you do not need to know the type of the object
you are printing. You do not have problems with format
strings not matching the argument list. (Though with gcc, you
do not have that problem with printf
either.) Streams
have automatic constructors and destructors that open and close the
relevant files.
Cons:
Streams make it difficult to do functionality like
pread()
. Some formatting (particularly the common
format string idiom %.*s
) is difficult if not
impossible to do efficiently using streams without using
printf
-like hacks. Streams do not support operator
reordering (the %1s
directive), which is helpful for
internationalization.
Decision:
Do not use streams, except where required by a logging interface.
Use printf
-like routines instead.
There are various pros and cons to using streams, but in this case, as in many other cases, consistency trumps the debate. Do not use streams in your code.
Extended Discussion
There has been debate on this issue, so this explains the
reasoning in greater depth. Recall the Only One Way
guiding principle: we want to make sure that whenever we
do a certain type of I/O, the code looks the same in all
those places. Because of this, we do not want to allow
users to decide between using streams or using
printf
plus Read/Write/etc. Instead, we should
settle on one or the other. We made an exception for logging
because it is a pretty specialized application, and for
historical reasons.
Proponents of streams have argued that streams are the obvious choice of the two, but the issue is not actually so clear. For every advantage of streams they point out, there is an equivalent disadvantage. The biggest advantage is that you do not need to know the type of the object to be printing. This is a fair point. But, there is a downside: you can easily use the wrong type, and the compiler will not warn you. It is easy to make this kind of mistake without knowing when using streams.
cout << this; // Prints the address cout << *this; // Prints the contents
The compiler does not generate an error because
<<
has been overloaded. We discourage
overloading for just this reason.
Some say printf
formatting is ugly and hard to
read, but streams are often no better. Consider the following
two fragments, both with the same typo. Which is easier to
discover?
cerr << "Error connecting to '" << foo->bar()->hostname.first << ":" << foo->bar()->hostname.second << ": " << strerror(errno); fprintf(stderr, "Error connecting to '%s:%u: %s", foo->bar()->hostname.first, foo->bar()->hostname.second, strerror(errno));
And so on and so forth for any issue you might bring up. (You could argue, "Things would be better with the right wrappers," but if it is true for one scheme, is it not also true for the other? Also, remember the goal is to make the language smaller, not add yet more machinery that someone has to learn.)
Either path would yield different advantages and
disadvantages, and there is not a clearly superior
solution. The simplicity doctrine mandates we settle on
one of them though, and the majority decision was on
printf
+ read
/write
.
++i
) of the increment and
decrement operators with iterators and other template objects.
size_t
where
appropriate. If a program needs a variable of a different size,
use a precise-width integer type from
<cstdint>
, such as int16_t
.
const
variables to macros.
The most important consistency rules are those that govern naming. The style of a name immediately informs us what sort of thing the named entity is: a type, a variable, a function, a constant, a macro, etc., without requiring us to search for the declaration of that entity. The pattern-matching engine in our brains relies a great deal on these naming rules.
Naming rules are pretty arbitrary, but we feel that consistency is more important than individual preferences in this area, so regardless of whether you find them sensible or not, the rules are the rules.
_
) or dashes (-
). Follow the
convention that your
project
uses. If there is no consistent local pattern to follow, prefer "_".
MyExcitingClass
, MyExcitingEnum
.
my_exciting_local_variable
,
my_exciting_member_variable
.
default_width
.
my_awesome_project
.
out_of_memory
, enclosed within an enum class.
MY_MACRO_THAT_SCARES_SMALL_CHILDREN
.
Though a pain to write, comments are absolutely vital to keeping our code readable. The following rules describe what you should comment and where. But remember: while comments are very important, the best code is self-documenting. Giving sensible names to types and variables is much better than using obscure names that you must then explain through comments.
When writing your comments, write for your audience: the next contributor who will need to understand your code. Be generous — the next one may be you!
TODO
comments for code that is temporary, a
short-term solution, or good-enough but not perfect.
Coding style and formatting are pretty arbitrary, but a project is much easier to follow if everyone uses the same style. Individuals may not agree with every aspect of the formatting rules, and some of the rules may take some getting used to, but it is important that all project contributors follow the style rules so that they can all read and understand everyone's code easily.
void
or auto
on the same line as function name,
parameters and return type on the same line if they fit.
{}
or continue
.
The coding conventions described above are mandatory. However, like all good rules, these sometimes have exceptions, which we discuss here.
Use common sense and BE CONSISTENT.
If you are editing code, take a few minutes to look at the
code around you and determine its style. If they use spaces
around their if
clauses, you should, too. If
their comments have little boxes of stars around them, make
your comments have little boxes of stars around them too.
The point of having style guidelines is to have a common vocabulary of coding so people can concentrate on what you are saying, rather than on how you are saying it. We present global style rules here so people know the vocabulary. But local style is also important. If code you add to a file looks drastically different from the existing code around it, the discontinuity throws readers out of their rhythm when they go to read it. Try to avoid this.
OK, enough writing about writing code; the code itself is much more interesting. Have fun!