Revision 0.1

Frank Michler
This style guide contains many details that are initially hidden from view. They are marked by the triangle icon, which you see here on your left. Click it now. You should see "Hooray" appear below.

Hooray! 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.

All header files should have #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:

#ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ ... #endif // FOO_BAR_BAZ_H_
Don't use an #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?

  • We can declare data members of type Foo* or Foo&.
  • We can declare (but not define) functions with arguments, and/or return values, of type Foo.
  • We can declare static data members of type 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.

Define functions inline only when they are small, say, 10 lines or less. You can declare functions in a way that allows the compiler to expand them inline rather than calling them through the usual function call mechanism. Inlining a function can generate more efficient object code, as long as the inlined function is small. Feel free to inline accessors and mutators, and other short, performance-critical functions. Overuse of inlining can actually make programs slower. Depending on a function's size, inlining it can cause the code size to increase or decrease. Inlining a very small accessor function will usually decrease code size while inlining a very large function can dramatically increase code size. On modern processors smaller code usually runs faster due to better use of the instruction cache.

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.

You may use file names with a -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.

When defining a function, parameter order is: inputs, then outputs.

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.

Function names, variable names, and filenames should be descriptive; eschew abbreviation. Types and variables should be nouns, while functions should be "command" verbs.

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:

int num_errors; // Good. int num_completed_connections; // Good.

Poorly-chosen names use ambiguous abbreviations or arbitrary characters that do not convey meaning:

int n; // Bad - meaningless. int nerr; // Bad - ambiguous abbreviation. int n_comp_conns; // Bad - ambiguous abbreviation.

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:

// Good // These show proper names with no abbreviations. int num_dns_connections; // Most people know what "DNS" stands for. int price_count_reader; // OK, price count. Makes sense. // Bad! // Abbreviations can be confusing or ambiguous outside a small group. int wgc_connections; // Only your group knows what this stands for. int pc_reader; // Lots of things can be abbreviated "pc".

Never abbreviate by leaving out letters:

int error_count; // Good. int error_cnt; // Bad.
Filenames should be all lowercase and can include underscores (_) 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:

url_table.hpp // The class declaration. url_table.cpp // The class definition. url_table-inl.hpp // Inline functions that include lots of code.

See also the section -inl.hpp Files

Type names start with a capital letter and have a capital letter for each new word, with no underscores: 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:

// classes and structs class UrlTable { ... class UrlTableTester { ... struct UrlTableProperties { ... // typedefs typedef hash_map<UrlTableProperties *, string> PropertiesMap; // enums enum UrlTableErrors { ...
Variable names should start with a capital letter and have a capital letter for each new word. No underscores, except for cases where underscores drastically increase readability. For instance: mNMDA_AMPA_Ratio. Class member variables start with leading letter "m". For instance: ExcitingLocalVariable, mExcitingMemberVariable.

For example:

string TableName; // OK - uses mixed case. string tablename; // Bad - all lowercase.

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".

string mTableName; // OK - underscore at end.

Data members in structs should be named like regular variables without the leading "m" that data members in classes have.

struct UrlTableProperties { string Name; int NumEntries; }

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.

Use a 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:

const int kDaysInAWeek = 7;
Regular functions have mixed case and start with lower case letter; accessors and mutators match the name of the variable: excitingFunction(), excitingMethod(), ExcitingMemberVariable(), setExcitingMemberVariable().

Functions should start with a lower case and have a capital letter for each new word. No underscores:

addTableEntry() deleteUrl()

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.

class MyClass { public: ... int NumEntries() const { return mNumEntries; } void setNumEntries(int Num_Entries) { mNumEntries = NumEntries; } private: int NumEntries; };

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.

Namespace names are all lower-case, and based on project names and possibly their directory structure: google_awesome_project.

See Namespaces for a discussion of namespaces and how to name them.

Enumerators should be named like constants kEnumName.

Preferably, the individual enumerators should be named like constants. The enumeration name, UrlTableErrors (and AlternateUrlTableErrors), is a type, and therefore mixed case.

enum UrlTableErrors { kOK = 0, kErrorOutOfMemory, kErrorMalformedInput, };
You're not really going to define a macro, are you? If you do, they're like this: 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.

#define ROUND(x) ... #define PI_ROUNDED 3.0
If you are naming something that is analogous to an existing C or C++ entity then you can follow the existing naming convention scheme.

bigopen()
function name, follows form of open()
uint
typedef
bigpos
struct or class, follows form of pos
sparse_hash_map
STL-like entity; follows STL naming conventions
LONGLONG_MAX
a constant, as in INT_MAX


Revision 0.1

Frank Michler