The ratio of time spent reading code vs. writing code is well over 10 to 1.
We're not teaching you to be a code monkey. We're teaching you to be a software developer: someone who writes good code and appreciates the importance of doing so.
That's why we're asking you to follow this style guide. Now, to be fair, there is no such thing as a perfect style guide: there will always be exceptions. But by and large, this guide has been designed to encourage good style with reasonable restriction.
Keep in mind that with the exception of the "General Guidelines" section, this guide has been developed with C++ in mind, so many rules are not portable (even to C/Java/C#, which are in the same syntax family!).
General Guidelines
In general (no matter what programming language), your code must be:
- consistent: any convention you apply in your code should be applied consistently throughout your code;
- well-structured: add exactly as much complexity to your code as it needs - if a function should be broken up, break it up, and if you should have another layer of abstraction, add it - but be sure to stop and ask yourself if your code should be written that way;
- self-documenting: use informative names that tell us what purpose a variable serves, what a function returns, etc.;
- commented meaningfully: comments are a supplement to your code and should enhance the reader's understanding of your code - explaining yourself is good (particularly when the code at hand is very complex, see this example in the illumos source code), and it's even better if you don't have to; and
- readable: it should be easy to understand your code simply by reading it, c.f.:
Remember: the purpose of these rules is to enhance your code, never to make it worse. We have designed these guidelines to be broadly applicable, but there are no rules without exceptions - there will always be judgment calls to make, and we expect you to be able to make the correct ones.
C++ Pitfalls
In addition, you should keep the following pieces of advice in mind:
- Think carefully about any overhead that might be involved in a seemingly innocuous operation.
- When incrementing and decrementing non-primitive types, it is generally preferable to use the prefix version, both because of the overhead and semantics of postfix.
- C++ has some very complex initialization semantics! As a general rule of thumb, direct initialization is preferred to copy initialization.
- C++ can be at time particularly annoying because of undefined (a.k.a. implementation-specific) behavior. You will be liable for any errors in your program arising from assumptions about undefined behavior.
- For instance, if you write
methodB(methodA(), methodA())
, there is no guarantee that the two calls tomethodA
will be made in order from left to right (there is in C++17, but we are using the C++11 standard for the purposes of this course).
Conventions
There are a lot of coding conventions out there, ranging from the absurd to the controversial. For the purposes of this course, we will be using a mix of conventions that can be enforced with the clang-format
linter and in-house rules (where ambiguous, ask, but it is safe to assume that in-house rules take precedence).
Using clang-format
You are expected to use clang-format -style=webkit
to lint your code, which will automatically format your code according to the WebKit coding standard, which can be found here.
Alternatively, you may obtain your own clang-format specifications.
Note that:
- some rules are specific to the WebKit project (e.g. the inclusion of
config.h
) and those may be ignored for the purposes of this course, - contrary to the WebKit standard, we expect you to use
#ifndef
/#include
/#endif
instead of pragma #once to prevent dependency duplication (see the section on include guards), and - the rules about
using
in the WebKit standard should be ignored; we have included guidelines about their usage in the in-house rules.
In-House Rules
- Maximum line width is 100 characters.
- Use Javadoc-style comments in the header file.
- Such comments are important because they define your API and lay out behavior specifications.
- This does not mean you should include every single comment field, but rather, it means your comments should be just extensive enough to allow someone to understand what a method does by reading the comments and looking at the method signature (e.g.
@details
is often unnecessary for simple methods/classes). Remember: comments are supplements to your code, c.f. - The Javadoc fields should all be vertically aligned, c.f.
- Classes should also be commented, c.f.
- Namespace contamination is not acceptable. This means:
- You are not allowed to use
using namespace std;
; nor are you allowed to import the contents of any namespace into the current namespace. You may import specific methods/variables with usingstd::cout;
. - Global variables are banned.
- Use of global functions is limited to
main()
, friend operator overloads, and where specified in an assignment.
Structure
Classes must be split between two files: a .h
containing only their declaration (the specification of the class's interface, fields, etc.) and a .cpp
containing only their definition (i.e. implementation of methods, etc.).
Header and Code Files
The use of both .h
and .cpp
files for the same bodies of code means that there are a number of stylistic concerns that must be managed:
- No file should contain code for more than one class.
- The
.h
and.cpp
for the same class should have matching filenames which also match the name of said class. - The order of member fields and functions should be preserved between the
.h
and the.cpp
files. - Function signatures in
.h
files should include the same parameter names used in the.cpp
file. - The
.h
should be included in the.cpp
file. - Note that in the case of template classes, you cannot #include the
.h
in the.cpp
, since this is at odds with the process by which template classes are instantiated; as such, in these cases, the.cpp
should be included at the end of the.h
file. - System headers should only be imported from the C++ standard library; it is not OK to import anything from the C standard library, particularly as everything in the C standard library has been ported to C++ (e.g.
<cstdlib>
replaces<stdlib.h>
). - System headers should only be imported using surrounding
<
and>
. Local code may be imported using either<library>
or"library"
.
Include Guards
To prevent the preprocessor from including dependencies multiple times, #include guards (a.k.a. #define
guards) must always be used where appropriate.
The WebKit standard specifies that #pragma once
should be used, which works for their purposes, but since it is simply a common compiler extension and not actually in the C++ standard, you are not allowed to use it.
In a .h
file, they must be named as NAMESPACE_CLASS_H
(if in a .cpp
, use_CPP
as the suffix), c.f.
If no namespace is used, do not prepend an underscore, c.f.
Declaration Order
When defining a class or struct, you should use access modifiers as the primary order, the following list as the secondary order, and grouping by functionality as the ternary order:
using
,typedef
,enum
- Constants (
static const
fields) - Big 3 (or 5 in C++11)
- Operator methods
- Other methods (
static
included) - Fields
Comments should also be used to distinguish logical groups of fields and/or functions.
Within functions, etc., variable declarations should be placed either at the start of a block (after the {
) or at the beginning of the group of lines of code that use the variables, and in the case of the latter, should be preceded by a blank line.
Access Modifiers
Never expose more of a class than should be exposed - in other words, if a member variable or function is not private
, then there should be a good reason for it to be public
or protected
.
Syntax and Semantics
C++ has been around for a long time, and also is a very powerful language. As a result, there are a number of things that you can do which - for the most part - you should generally be unable to do, or at the very least you should avoid doing. These rules are as follows:
- Pointers should be used only for dynamically allocated objects.
- The appropriate data type should be used for the appropriate data:
- e.g.
bool
for true/false,ptrdiff_t
for pointer arithmetic,size_t
for values returned bysizeof
. - Where appropriate, architecture-independent data types should be used, e.g.
uint32_t
for integers. - Functions may be collapsed into one-liners if and only if they contain no executable statements, e.g.
void foo() {}
. - Use base member initialization whenever possible.
- Use default parameters whenever possible.
- Modern compilers can do a lot of work for you that you might not actually want them to do! For fully deterministic behavior, you should use explicit constructors and operators when possible.
- The safe bool idiom is one of the many practices that we were able to do away with when
explicit
was introduced in C++11. - Avoid using the
inline
keyword unless you understand the implications thereof for compiler optimization, static linking, and dynamic linking. - Always use
nullptr
when referring to the null pointer, neverNULL
or0
. - Use
const
liberally where it makes sense to do so. - A method signature
void goodMethod(const Foo *param);
is very meaningful - it tells the caller that they can expect aFoo
instance to be the same before and after callingsomeMethod()
on it!! - By contrast, the const in
void badMethod(Foo *const var);
has absolutely no meaning whatsoever for the caller, and should be omitted. - Use pass-by-reference exactly where it is appropriate to do so. The purpose of passing by reference is to reduce the overhead of a function call, not to make your code harder to read.
Naming Fields and Functions
We expect SCREAMING_SNAKE_CASE
on constants and camelCase
on everything else; specifically, UpperCamelCase
for classes and namespaces and lowerCamelCase
for class fields, locals, and functions.
Names should also generally correspond to what a variable represents and to what a function does or returns. To that end, variable names should generally be nouns and function names should generally be adjectives and/or verbs (generally, “is” should prefix boolean accessors, “get” for other accessors, and “set” for mutators), e.g.
Avoid names which might be ambiguous, e.g. void setRelativeZero()
and void setCounterToZero()
are preferable to void zeroCounter()
.
Spacing
The usage of whitespace is really important and is absolutely huge for readability. Use blank lines liberally (but not excessively) to improve readability; it is far more useful to be able to distinguish logical groups of member functions at a glance than to have to scroll for them.
There are also a number of issues that you should be aware of:
- Trailing whitespace should be trimmed, because it screws with diffing (and by consequence, also messes up Git diffs and thereby can introduce severe complications to merging etc.). Unfortunately
clang-format
does not support this. - In nested templates, prior to C++11, compilers were unable to interpretand spaces were needed to help the compiler identify templates, c.f.due to tokenization rules in the C++ language standard. The C++11 standard - which redefined tokenization rules so that the former template declaration was valid - was not fully supported until GCC 4.9 and Clang 3.3, however, so be aware that using the former can be problematic in legacy code. Use either as befits your preference.