Revision 0.1
Frank MichlerHooray! Now you know you can expand points to get more details. Alternatively, there's an "expand all" at the top of this document.
The objsim library is written in C++. This style guide is adapted from the "Google C++ Style Guide".
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
.hpp
file.
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>_HPP_
.
To guarantee uniqueness, they should be based on the full path
in a project's source tree. For example, the file
foo/src/bar/baz.hpp
in project foo
should
have the following guard:
#include
when a forward declaration
would suffice.
When you include a header file you introduce a dependency that will cause your code to be recompiled whenever the header file changes. If your header file includes other header files, any change to those files will cause any code that includes your header to be recompiled. Therefore, we prefer to minimize includes, particularly includes of header files in other header files.
You can significantly minimize the number of header files you
need to include in your own header files by using forward
declarations. For example, if your header file uses the
File
class in ways that do not require access to
the declaration of the File
class, your header
file can just forward declare class File;
instead
of having to #include "file/base/file.hpp"
.
How can we use a class Foo
in a header file
without access to its definition?
Foo*
or
Foo&
.
Foo
.
Foo
. This is because static data members
are defined outside the class definition.
On the other hand, you must include the header file for
Foo
if your class subclasses Foo
or
has a data member of type Foo
.
Sometimes it makes sense to have pointer (or better,
scoped_ptr
)
members instead of object members. However, this complicates code
readability and imposes a performance penalty, so avoid doing
this transformation if the only purpose is to minimize includes
in header files.
Of course, .cpp
files typically do require the
definitions of the classes they use, and usually have to
include several header files.
A decent rule of thumb is to not inline a function if it is more than 10 lines long. Beware of destructors, which are often longer than they appear because of implicit member- and base-destructor calls!
Another useful rule of thumb: it's typically not cost effective to inline functions with loops or switch statements (unless, in the common case, the loop or switch statement is never executed).
It is important to know that functions are not always inlined even if they are declared as such; for example, virtual and recursive functions are not normally inlined. Usually recursive functions should not be inline. The main reason for making a virtual function inline is to place its definition in the class, either for convenience or to document its behavior, e.g., for accessors and mutators.
-inl.hpp
suffix to define
complex inline functions when needed.
The definition of an inline function needs to be in a header
file, so that the compiler has the definition available for
inlining at the call sites. However, implementation code
properly belongs in .cpp
files, and we do not like
to have much actual code in .hpp
files unless there
is a readability or performance advantage.
If an inline function definition is short, with very little,
if any, logic in it, you should put the code in your
.hpp
file. For example, accessors and mutators
should certainly be inside a class definition. More complex
inline functions may also be put in a .hpp
file for
the convenience of the implementer and callers, though if this
makes the .hpp
file too unwieldy you can instead
put that code in a separate -inl.hpp
file.
This separates the implementation from the class definition,
while still allowing the implementation to be included where
necessary.
Another use of -inl.hpp
files is for definitions of
function templates. This can be used to keep your template
definitions easy to read.
Do not forget that a -inl.hpp
file requires a
#define
guard just
like any other header file.
Parameters to C/C++ functions are either input to the
function, output from the function, or both. Input parameters
are usually values or const
references, while output
and input/output parameters will be non-const
pointers. When ordering function parameters, put all input-only
parameters before any output parameters. In particular, do not add
new parameters to the end of the function just because they are
new; place new input-only parameters before the output
parameters.
This is not a hard-and-fast rule. Parameters that are both input and output (often classes/structs) muddy the waters, and, as always, consistency with related functions may require you to bend the rule.
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.
Give as descriptive a name as possible, within reason. Do not worry about saving horizontal space as it is far more important to make your code immediately understandable by a new reader. Examples of well-chosen names:
Poorly-chosen names use ambiguous abbreviations or arbitrary characters that do not convey meaning:
Type and variable names should typically be nouns: e.g.,
FileOpener
,
num_errors
.
Function names should typically be imperative (that is they
should be commands): e.g., OpenFile()
,
set_num_errors()
. There is an exception for
accessors, which, described more completely in Function Names, should be named
the same as the variable they access.
Do not use abbreviations unless they are extremely well known outside your project. For example:
Never abbreviate by leaving out letters:
_
) or dashes (-
). Follow the
convention that your
project
uses. If there is no consistent local pattern to follow, prefer "_".
Examples of acceptable file names:
my_useful_class.cpp
my-useful-class.cpp
myusefulclass.cpp
C++ files should end in .cpp
and header files
should end in .hpp
.
Do not use filenames that already exist
in /usr/include
, such as db.h
.
In general, make your filenames very specific. For example,
use http_server_logs.h
rather
than logs.h
. A very common case is to have a
pair of files called, e.g., foo_bar.hpp
and foo_bar.cpp
, defining a class
called FooBar
.
Inline functions must be in a .hpp
file. If your
inline functions are very short, they should go directly into your
.hpp
file. However, if your inline functions
include a lot of code, they may go into a third file that
ends in -inl.hpp
. In a class with a lot of inline
code, your class could have three files:
See also the section -inl.hpp Files
MyExcitingClass
, MyExcitingEnum
.
The names of all types — classes, structs, typedefs, and enums — have the same naming convention. Type names should start with a capital letter and have a capital letter for each new word. No underscores. For example:
mNMDA_AMPA_Ratio
. Class member variables start with leading letter "m". For
instance: ExcitingLocalVariable
,
mExcitingMemberVariable
.
For example:
Data members (also called instance variables or member variables) are lowercase with optional underscores like regular variable names, but always start with a leading letter "m".
Data members in structs should be named like regular variables without the leading "m" that data members in classes have.
See Structs vs. Classes for a discussion of when to use a struct versus a class.
There are no special requirements for global variables,
which should be rare in any case, but if you use one,
consider prefixing it with g_
or some other
marker to easily distinguish it from local variables.
k
followed by mixed case:
kDaysInAWeek
.
All compile-time constants, whether they are declared locally,
globally, or as part of a class, follow a slightly different
naming convention from other variables. Use a k
followed by words with uppercase first letters:
excitingFunction()
,
excitingMethod()
,
ExcitingMemberVariable()
,
setExcitingMemberVariable()
.
Functions should start with a lower case and have a capital letter for each new word. No underscores:
Accessors and mutators (get and set functions) should match
the name of the variable they are getting and setting. This
shows an excerpt of a class whose instance variable is
mNumEntries
.
You may also use lowercase letters for other very short inlined functions. For example if a function were so cheap you would not cache the value if you were calling it in a loop, then lowercase naming would be acceptable.
google_awesome_project
.
See Namespaces for a discussion of namespaces and how to name them.
kEnumName
.
Preferably, the individual enumerators should be named like
constants. The enumeration name,
UrlTableErrors
(and
AlternateUrlTableErrors
), is a type, and
therefore mixed case.
MY_MACRO_THAT_SCARES_SMALL_CHILDREN
.
Please see the description of macros; in general macros should not be used. However, if they are absolutely needed, then they should be named like enum value names with all capitals and underscores.
bigopen()
open()
uint
typedef
bigpos
struct
or class
, follows form of
pos
sparse_hash_map
LONGLONG_MAX
INT_MAX
Revision 0.1
Frank Michler