-
Notifications
You must be signed in to change notification settings - Fork 1
CPP_Style_Guide
C++ is the main development language used by nomlib.
The goal of this guide is to manage the many complexities of C++ by describing in detail the dos and don'ts of writing C++ code for this library. These rules exist to keep the code base manageable while still allowing coders to use C++ language features productively.
This this guide is not a C++ tutorial: I assume that the reader is familiar with the language and the terminology used below.
Many thanks goes to the Google C++ Style Guide for making this styling guide possible in such a short manner of time, with what I feel is a concise, if not overly concise source of information for nomlib developers. Shamelessly, I have mostly copied and pasted verbatim from the guide, and then modify the information as necessary to suit this project.
In general, every .cpp file should have an associated .hpp file. There are some common exceptions, such as unit tests and small .cpp files containing just a main() function.
All header files must include a #define guard to prevent multiple inclusion, with a comment depicting the end of the preprocessing block. The format of the symbol name should be <PROJECT>_<FILE>_HPP.
#ifndef NOMLIB_TEMPLATE_HPP
#define NOMLIB_TEMPLATE_HPP
// ...
#endif // include guard defined
You may forward declare ordinary classes and structs in order to avoid unnecessary #includes.
- When using a function declared in a header file, always #include that header.
- When using a class template, prefer to #include its header file.
- When using an ordinary class, relying on a forward declaration is OK, but be wary of situations where a forward declaration may be insufficient or incorrect; when in doubt, just #include the appropriate header.
- Do not replace data members with pointers just to avoid an #include.
[TODO: Example]
Paramater order should be: inputs, then outputs.
- Input parameters are usually values or const references whereas output and input/output parameters will generally be non-const pointers.
- Paramaters 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.
Use standard order for readability and to avoid hidden dependencies: C/C++ libraries, other libraries and lastly, project header files.
All of a project's header files should be listed as descendants of the project's source directory without use of UNIX directory shortcuts . (the current directory) or .. (the parent directory).
#include "nomlib/system/File.hpp"
- Except for standard C/C++ libaries, double quotes should be used.
- Sometimes, system-specific code needs conditional includes. Such code should put these includes after other includes where possible.
[TODO: Example]
Unnamed namespaces in .cpp files are strongly discouraged. The name of namespaces should be based on the project and possibly its path. Do not use a using-directive. Do not use inline namespaces.
- Named namespaces should be nested under the base nom namespace.
namespace nom {
// ...
} // namespace nom
- Do not use unnamed namespaces in .hpp files.
- Do not declare anything in namespace std, not even forward declarations of a standard library classes. To declare entities from the standard library, include the appropiate header file(s).
Declaring entities in namespace std is undefined behavior, i.e., not portable.
- Namespace aliases are allowed anywhere in a .cpp, anywhere inside the named namespace that wraps an entire .hpp file, and in functions and methods.
Note that an alias in a .hpp file is visible to everyone including that file, so public headers (those available outside of a project) and headers transitively included by them, should avoid definining aliases, as part of the general goal of keeping public APIs as small as possible.
namespace nom {
namespace json {
// ...
} // namespace json
} // namespace nom
Prefer non-member functions within a namespace or static member functions to a global function; use completely global functions rarely.
Place a function's variables in the narrowest scope possible, and initialize variables in the declaration.
int i;
i = f(); // Bad -- initialization is separate from declaration.
int i = f(); // Correct
std::vector<int> v;
v.push_back ( 1 ); // Bad -- initialization is separate from declaration.
std::vector<int> v = { 1, 2 }; // Good -- v starts initialized.
If the variable is an object, its constructor is invoked every time it enters scope and is created, and its destructor is invoked every time it goes out of scope.
// Inefficient implementation:
for (int i = 0; i < 1000000; ++i) {
Foo f; // My ctor and dtor get called 1000000 times each.
f.DoSomething(i);
}
It may be more efficient to declare such a variable used in a loop outside that loop:
Foo f; // My ctor and dtor get called once each.
for (int i = 0; i < 1000000; ++i) {
f.DoSomething(i);
}
Static or global variables of class type are forbidden: they cause hard-to-find bugs due to indeterminate order of construction and destruction. However, such variables are allowed if they are constexpr: they have no dynamic initialization or destruction.
Avoid doing complex initialization in constructors (in particular, initialization that can fail or that require virtual method calls).
- Always add a constructor to the class, even if the constructor does nothing. Yes, even if the class is a pure abstract interface.
class IDrawable
{
public:
IDrawable ( void ) {}
};
Always add the destructor to the class, even if the destructor does nothing. Yes, even if the class is a pure abstract interface.
class Sprite
{
public:
~Sprite ( void ) {}
};
If your class defines member variables, you must provide an in-class initializer for every member variable or write a constructor (which can be a default constructor). If you do not declare any constructors yourself then the compiler will generate a default constructor for you, which may leave some fields uninitialized or initialized to inappropriate values.
[TODO]
Use delegating and inheriting constructors when they reduce boilerplate and improve readability. Be cautious about inheriting constructors when your derived class has new member variables. Inheriting constructors may still be appropriate in that case if you can use in-class member initialization for the derived class's member variables.
Use a struct only for passive objects that carry data; everything else is a class.
Prefer composition to inheritance. Use inheritance when it makes most sense. When using inheritance, make it public.
- Do not overuse implementation inheritance; try to restrict use to "is-a" case:
class Sprite
{
// ...
};
class SpriteBatch:
public Sprite // "is-a" Sprite
{
// ...
};
class AnimatedSprite:
public SpriteBatch // "is-a" SpriteBatch
{
// ...
};
-
Make your destructor virtual when appropriate. If your class has virtual methods, its destructor should be virtual. If you anticipate your class to be inherited, your destructor should be virtual.
-
Limit the use of protected to those member functions that might need to be accessed from subclasses. Note that [data members should be private](#Access Control).
-
When redefining an inherited virtual function, explicitly declare it virtual in the declaration of the derived class. Rationale: If virtual is omitted, the reader has to check all ancestors of the class in question to determine if the function is virtual or not.
Only very rarely is multiple implementation inheritance more useful than it is dangerous. It is only acceptable when, at most, one of the base classes has an implementation; all other base classes must be pure abstract interface classes tagged with the I prefix.
All interface classes must begin with the I prefix.
[TODO]
Make data members private, and provide access to them through accessor functions as needed. [TOOD: Typically a variable would be called foo_ and the accessor function foo(). You may also want a mutator function set_foo(). Exception: static const data members need not be private.
[TODO: The definitions of accessors are usually inlined in the header file.]
See also Inheritance and [Function Names](#Function Names).
Use the specified order of declarations within a class: public: before private:, methods before data members (variables), etc.
Your class definition should start with its public: section, followed by its protected: section and then its private: section. If any of these sections are empty, omit them.
Within each section, the declarations generally should be in the following order:
- Typedefs and Enums
- Constructors
- Destructor
- Methods, including static methods
- Data Members (except static const data members)
Friend declarations should always be in the private section.
Method definitions in the corresponding .cpp file should be the same as the declaration order, as much as possible.
Do not put large method definitions inline in the class definition. Usually, only trivial or performance-critical, and very short, methods may be defined inline. See [Inline Functions] for more details.
Prefer small and focused functions.
[TODO: additional description?]
Prefer to have single, fixed owners for dynamically allocated objects. Prefer to transfer ownership with smart pointers.
If dynamic allocation is necessary, prefer to keep ownership with the code that allocated it. If other code needs access to the object, consider passing it a copy, or passing a pointer or reference without transferring ownership. Prefer to use std::unique_ptr to make ownership transfer explicit.
#include <memory>
std::unique_ptr<Foo> FooFactory ();
void FooConsumer ( std::unique_ptr<Foo> ptr );
Do not design your code to use shared ownership without reason. One such reason is to avoid expensive copy operations, but you should only do this if the performance benefits are significant, and the underlying object is immutable (i.e. std::shared_ptr). If you do use shared ownership, prefer to use std::shared_ptr.
Do not use boost::scoped_ptr in new code unless you need to be compatible with older versions of C++. Never use linked_ptr or std::auto_ptr. In all three cases, use std::unique_ptr instead.
[TODO: Use cpplint.py to detect style errors?]
All parameters passed by reference should be labeled const.
Prefer to not use default function paramaters. Prefer function overloading instead, if appropriate.
OK to use friend classes and functions, within reason.
Most classes should interact with other classes solely through their public members. However, in some cases friends are better than making a member public when you want to give only one other class access to it.
Usage of C++ exceptions are not allowed due to (possibly biased?) performance concerns.
[TODO]
Use C++ casts like static_cast<>(). Do not use other cast formats like **int y = ( int ) x; or int y = int ( x );.
Use prefix form (++i) of the increment and decrement operators with iterators and other template objects.
Prefer to use const wherever it makes sense.
- If a function does not modify an argument passed by reference or by pointer, that argument should be const.
- Declare methods to be const whenever possible. Accessors should almost always be const. Other methods should be const if they do not modify any data members, do not call any non-const methods, and do not return a non-const pointer or non-const reference to a data member.
- Consider making data members const whenever they do not need to be modified after construction.
The mutable keyword is acceptable, within practical reason and kept to a minimum necessity.
The keyword const must come first.
[TODO: reconsider?]
In C++11, use constexpr to define true constants or to ensure constant initialization.
Of the built-in C++ integer types, the only one used is int. If a program needs a variable of a different size, you must use the precise-width integer types from nomlib/types.hpp, such as nom::int32. If your variable represents a value that could ever be greater than or equal to 2^31 (2GiB), use a 64-bit type such as nom::int64. Keep in mind that even if your value won't ever be too large for an int, it may be used in intermediate calculations which may require a larger type. When in doubt, choose a larger type. If nomlib/types.hpp does not contain the definition you need, prefer defined types.
-
You should not use the unsigned integer types such as nom::uint32, unless there is a valid reason such as representing a bit pattern rather than a number, or you need defined overflow modulo 2^N. In particular, do not use unsigned types to say a number will never be negative. Instead, use assertions for this.
-
If your code is a container that returns a size, be sure to use a type that will accommodate any possible usage of your container. When in doubt, use a larger type rather than a smaller type.
-
Use care when converting integer types. Integer conversions and promotions can cause non-intuitive behavior.
An example of non-intuitive behavior is one particular self-perceived bug that plagued my mind when using unsigned 8-bit integers -- nom::uint8. I incorrectly assumed that std::cout should print these values as integers, when in truth, these values are, in fact, unsigned 8-bit characters -- unsigned char.
Some people -- including some textbook authors and even my own assumption during the early development cycle -- recommend using unsigned types to represent numbers that are never negative. This is intended as a form of self-documentation. However, the advantages of such documentation are outweighed by the real bugs it can introduce. Consider:
for ( unsigned int i = foo.Length () - 1; i >= 0; --i )
{
// ...
}
This code will never terminate! Sometimes gcc will notice this bug and warn you, but often it will not. Equally bad bugs can occur when comparing signed and unsigned variables. Basically, the type-promotion scheme causes unsigned types to behave differently than one might expect.
So, document that a variable is non-negative using assertions. Don't use an unsigned type.
Code should be 64-bit and 32-bit friendly. Bear in mind problems of printing, comparisons, and structure alignment.
[TODO]
Be very cautious with macros. Prefer inline functions, enums, and const variables to macros.
-
Instead of using a macro to inline performance-critical code, use an inline function.
-
Instead of using a macro to store a constant, use a const variable.
-
Instead of using a macro to "abbreviate" a long variable name, use a reference.
-
Instead of using a macro to conditionally compile code ... well, don't do that at all -- except, of course, for the #define guards to prevent double inclusion of header files. It makes testing much more difficult.
-
Before using a macro, consider carefully whether there's a non-macro way to achieve the same result.
The following usage pattern will avoid many problems with macros -- if you use macros, follow it when possible:
-
Don't define macros in a .hpp file.
-
#define macros right before you use them, and #undef them right after.
-
Do not just #undef an existing macro before replacing it with your own; instead, pick a name that's likely to be unique.
-
Try not to use macros that expand to unbalanced C++ constructs, or at least document that behavior well.
-
Prefer not using ## to generate function/class/variable names.
Use 0 for integers, 0.0 for reals, nullptr for pointers, and '\0' for chars.
Use auto to avoid type names that are just clutter. Continue to use manifest type declarations when it helps readability, and never use auto for anything but local variables.
- C++ type names can sometimes be long and cumbersome, especially when they involve templates or namespaces. In a statement like:
std::map<std::string, int>::iterator iter = m.find ( val );
the return type is hard to read, and obscures the primary purpose of the statement. Changing it to:
auto iter = m.find ( val );
makes it more readable.
Brace initializations are OK.
In C++11, this syntax is expanded for use with all other datatypes. The brace initialization form is called braced-init-list. Here are a few examples of its use:
std::vector<std::string> v { "foo", "bar" };
// The same, except this form cannot be used if the initializer_list
// constructor is explicit. You may choose to use either form.
std::vector<std::string> v = { "foo", "bar" };
// Maps take lists of pairs. Nested braced-init-lists work.
std::map<int, std::string> m = { { 1, "one" }, { 2, "2" } };
// braced-init-lists can be implicitly converted to return types.
std::vector<int> test_function ( void )
{
return {1, 2, 3};
}
// Call a function using a braced-init-list.
void test_function2 ( std::vector<int> v ) {}
test_function2 ( { 1, 2, 3 } );
- Never assign a braced-init-list to an auto local variable. In the single element case, what this means can be confusing.
auto d = { 1.23}; // Bad -- d is now an std::initializer_list<double>
auto d == double { 1.23 }; // Good -- d is a double, not an std::initializer_list
[TODO]
Use of the Boost library collection is OK, but one should consider using the superseded equivalents available in C++11 whenever possible -- i.e. use std::array instead of boost::array, and std::unique_ptr or std::shared_ptr instead of boost:ptr.
Use libraries and language extensions from C++11 (formerly known as C++0x) when appropriate.
- Portability of nomlib to other environments -- such as the Android and iOS platforms -- is a future consideration, and whenever knowledgeable, consider this before using C++11 features.
The most important consistency rules are those governing the naming scheme. Naming rules are quite arbitrary, but consistency must be valued above all else.
Function names, variable names, and filenames should be descriptive; eschew abbreviation.
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. Do not use abbreviations that are ambiguous or unfamiliar to readers outside your project, and do not abbreviate by deleting letters within a word.
int price_count_reader; // Good -- no abbreviation.
int num_errors; // OK -- "num" is a widespread convention.
int num_dns_connections; // OK -- Most people know what "DNS" stands for.
int n; // Bad -- Meaningless.
int nerr; // Bad -- Ambiguous abbreviation.
int n_comp_conns; // Bad -- Ambiguous abbreviation.
int wgc_connections; // Bad -- Only your group knows what this stands for.
int pc_reader; // Bad -- Lots of things can be abbreviated "pc".
int cstmr_id; // Bad -- Deletes internal letters.
File names of classes should follow the same naming convention as [Classes], whereas all other file names must be all lowercase. Underscores (_) and dashes (-) are acceptable and even encouraged whereever greater readability is achieved. Spacing in the file name are strictly forbidden.
-
C++ files must end in .cpp and header files must end in .hpp.
-
Source files must be placed under the src directory. Header files must be placed in the include/nomlib directory.
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 std::hash_map<UrlTableProperties *, std::string> PropertiesMap;
// enums
enum UrlTableErrors
{
// ...
}
Variable names are all lowercase, with underscores between words. Class member variables have trailing underscores. For instance: my_exciting_local_variable, my_exciting_member_variable_.
For example:
std::string tableName; // Bad - mixed case.
std::string table_name; // Good - uses underscore.
std::string tablename; // Good - all lowercase.
Data members -- also called instance variables or member variables -- are lowercase with optional underscores like regular variable names.
string tableName; // Bad - mixed case.
string tablename; // Bad -- no underscore between words.
string table_name; // Good.
string table_name_; // OK to use underscores at the end.
[TODO: consider enforcement of trailing underscores.]
Data members in structs should be named like regular variables.
[TODO: Data members in structs should be named like regular variables without the trailing underscores that data members in classes have.]
struct UrlTableProperties
{
std::string name;
int num_entries;
}
See [Structs vs. Classes] for a discussion of when to use a struct versus a class.
Constant names must be all uppercase, with underscores in between the words.
const int DAYS\_IN\_THE_WEEK = 7;
Function names must follow the same rules as [Variable Names].
Class names must follow the same rules as [Type Names].
Accessors and mutators -- getter and setter functions -- should match the name of the variable they are getting and setting. This shows an excerpt of a class whose instance variable is num_entries_.
class MyClass
{
public:
// ...
int num_entries() const
{
return num_entries_;
}
void set_num_entries ( int num_entries )
{
num_entries_ = num_entries;
}
private:
int num_entries_;
};
Namespace names are all lower-case, and based on project names and possibly their directory structure: nom::json, nom::ui.
- See also [Namespaces].
Enumerators should be named like [Class Names], i.e.: EnumName.
Macros must be in all uppercase letters, following the same rules as [Constant Names](#Constant Names).
#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.
Whereas 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, try to write for your audience: the next contributor who will need to understand your code.
Use either the // or / / syntax, as long as you are consistent.
Start each file with the description of nomlib as per the github description, followed by the license boilerplate.
-
Every file must contain the license boilerplate
-
If you make significant changes to a file with an author line, consider deleting the author line.
[TODO: legal consultation]
/******************************************************************************
nomlib - C++11 cross-platform game engine
Copyright (c) 2013, 2014 Jeffrey Carpenter <[email protected]>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
Every class definition should have an accompanying comment that describes what it is for.
// Platform-agnostic file path handling
class
{
// ...
};
Declaration comments describe use of the function; comments at the definition of a function describe operation.
Every function declaration should have comments immediately preceding it that describe what the function does and how to use it. These comments should be descriptive ("Opens the file") rather than imperative ("Open the file"); the comment describes the function, it does not tell the function what to do. In general, these comments do not describe how the function performs its task. Instead, that should be left to comments in the function definition.
Types of things to mention in comments at the function declaration:
-
What the inputs and outputs are.
-
For class member functions: whether the object remembers reference arguments beyond the duration of the method call, and whether it will free them or not.
-
If the function allocates memory that the caller must free.
-
Whether any of the arguments can be a null pointer.
-
If there are any performance implications of how a function is used.
-
If the function is re-entrant. What are its synchronization assumptions?
// Platform-agnostic file path handling
class
{
// ...
// Resize the video surface, using an available rescaling algorithm.
//
// See the ResizeAlgorithm enum for choices.
bool resize ( enum ResizeAlgorithm scaling_algorithm );
};
-
However, do not be unnecessarily verbose or state the completely obvious. Notice below that it is not necessary to say "returns false otherwise" because this is implied.
-
When commenting constructors and destructors, remember that the person reading your code knows what constructors and destructors are for, so comments that just say something like "destroys this object" are not useful. Document what constructors do with their arguments (for example, if they take ownership of pointers), and what cleanup the destructor does. If this is trivial, you may skip the comment. It is quite common for destructors not to have a header comment.
-
If there is anything tricky about how a function does its job, the function definition should have an explanatory comment. For example, in the definition comment you might describe any coding tricks you use, give an overview of the steps you go through, or explain why you chose to implement the function in the way you did rather than using a viable alternative. For instance, you might mention why it must acquire a lock for the first half of the function but why it is not needed for the second half.
-
Note you should not just repeat the comments given with the function declaration, in the .hpp file or wherever. It's okay to recapitulate briefly what the function does, but the focus of the comments should be on how it does it.
In general the actual name of the variable should be descriptive enough to give a good idea of what the variable is used for. In certain cases, more comments are required.
Each class data member (also called an instance variable or member variable) should have a comment describing what it is used for. If the variable can take sentinel values with special meanings, such as a null pointer or -1, document this. For example:
private:
// Keeps track of the total number of entries in the table.
// Used to ensure we do not go over the limit. -1 means
// that we don't yet know how many entries the table has.
int num_total_entries_;
As with data members, all global variables should have a comment describing what they are and what they are used for. For example:
// The total number of tests cases that we run through in this regression test.
const int NUM_TEST_CASES = 6;
In your implementation you should have comments in tricky, non-obvious, interesting, or important parts of your code.
Tricky or complicated code blocks should have comments before them. Example:
// Divide result by two, taking into account that x
// contains the carry from the add.
for ( int i = 0; i < result->size(); i++)
{
x = ( x << 8 ) + ( * result )[ i ];
( * result )[ i ] = x >> 1;
x& = 1;
}
Also, lines that are non-obvious should get a comment at the end of the line. These end-of-line comments should be separated from the code by 2 spaces. Example:
// If we have enough memory, mmap the data portion too.
mmap_budget = std::max<nom::int64>(0, mmap_budget - index_->length() );
if ( mmap_budget >= data_size_ && ! MmapData ( mmap_chunk_bytes, mlock ) )
{
return; // Error already logged.
}
Note that there are both comments that describe what the code is doing, and comments that mention that an error has already been logged when the function returns.
If you have several comments on subsequent lines, it can often be more readable to line them up:
DoSomething(); // Comment here so the comments line up.
DoSomethingElseThatIsLonger(); // Comment here so there are two spaces between
// the code and the comment.
{ // One space before comment when opening a new scope is allowed,
// thus the comment lines up with the following comments and code.
DoSomethingElse(); // Two spaces before line comments normally.
}
DoSomething(); /* For trailing block comments, one space is fine. */
When you pass in a null pointer, boolean, or literal integer values to functions, you should consider adding a comment about what they are, or make your code self-documenting by using constants. For example, compare:
bool success = CalculateSomething ( interesting_value,
10,
false,
nullptr
); // Bad -- What are these arguments??
versus:
```c++
bool success = CalculateSomething ( interesting_value,
10, // Default base value.
false, // Not the first time we're calling this.
nullptr // No callback.
);
Or alternatively, constants or self-describing variables:
const int DEFAULT_BASE_VALUE = 10;
const bool FIRST_TIME_CALLING = false;
Callback *null_callback = nullptr;
bool success = CalculateSomething ( interesting_value,
DEFAULT_BASE_VALUE,
FIRST_TIME_CALLING,
null_callback
);
Note that you should never describe the code itself. Assume that the person reading the code knows C++ better than you do, even though he or she does not know what you are trying to do:
// Now go through the b array and make sure that if i occurs,
// the next element is i + 1.
// ...
// Geez. What a useless comment.
Pay attention to punctuation, spelling, and grammar; it is easier to read well-written comments than badly written ones.
-
Comments should be as readable as narrative text, with proper capitalization and punctuation. In many cases, complete sentences are more readable than sentence fragments. Shorter comments, such as comments at the end of a line of code, can sometimes be less formal, but you should be consistent with your style.
-
Although it can be frustrating to have a code reviewer point out that you are using a comma when you should be using a semicolon, it is very important that source code maintain a high level of clarity and readability. Proper punctuation, spelling, and grammar help with that goal.
Use TODO comments for code that is temporary, a short-term solution, or good-enough but not perfect.
- TODOs should include the string TODO in all caps, followed by the name, e-mail address, or other identifier of the person who can best provide context about the problem referenced by the TODO. A colon is optional. The main purpose is to have a consistent TODO format that can be searched to find the person who can provide more details upon request. A TODO is not a commitment that the person referenced will fix the problem. Thus when you create a TODO, it is almost always your name that is given.
// TODO ([email protected]): Use a "*" here for concatenation operator.
// TODO (Zeke): Change this to use relations.
Mark deprecated interface points with DEPRECATED comments.
-
You can mark an interface as deprecated by writing a comment containing the word DEPRECATED in all caps. The comment should go before the declaration of the interface.
-
A deprecation comment must include simple, clear directions for people to fix their call-sites. You may implement a deprecated function as an inline function that calls the new interface point.
-
New code should not contain calls to deprecated interface points. Use the new interface point instead.
Each line of text in your code should be, at most, 80 characters long.
-
Exception: if a comment line contains an example command or a literal URL longer than 80 characters, that line may be longer than 80 characters for ease of cut and paste.
-
Exception: an #include statement with a long path may exceed 80 columns. Try to avoid situations where this becomes necessary.
-
Exception: you needn't be concerned about header guards that exceed the maximum length.
Non-ASCII characters should be rare, and must use UTF-8 formatting.
-
You shouldn't hard-code user-facing text in source, even English, so use of non-ASCII characters should be rare. However, in certain cases it is appropriate to include such words in your code. For example, if your code parses data files from foreign sources, it may be appropriate to hard-code the non-ASCII string(s) used in those data files as delimiters. More commonly, unit test code -- which does not need to be localized -- might contain non-ASCII strings. In such cases, you should use UTF-8, since that is an encoding understood by most tools able to handle more than just ASCII.
-
Hex encoding is also OK, and encouraged where it enhances readability -- for example, "\xEF\xBB\xBF", or, even more simply, u8"\uFEFF", is the Unicode zero-width no-break space character, which would be invisible if included in the source as straight UTF-8.
-
Use the u8 prefix to guarantee that a string literal containing \uXXXX escape sequences is encoded as UTF-8. Do not use it for strings containing non-ASCII characters encoded as UTF-8, because that will produce incorrect output if the compiler does not interpret the source file as UTF-8.
-
You shouldn't use the C++11 char16_t and char32_t character types, since they're for non-UTF-8 text. For similar reasons you also shouldn't use wchar_t -- unless you're writing code that interacts with the Windows API, which uses wchar_t extensively.
Use only spaces, and indent 2 spaces at a time.
- Do not use tabs in your code. It is suggested that you set your editor to emit spaces when you hit the tab key.
Return type on the same line as function name, parameters on the same line if they [fit](#Line length).
Functions look like this:
ReturnType ClassName::function_name ( Type par_name1, Type par_name2 )
{
// ...
}
If you have too much text to fit on one line:
ReturnType ClassName::really_long_function_name (
Type par_name1, Type par_name2,
Type par_name3
)
{
// ...
}
or if you cannot fit even the first parameter:
ReturnType LongClassName::really_really_really_really_long_function_name
(
Type par_name1,
Type par_name2,
Type par_name3
)
{
// ...
}
Some points to note:
-
If you cannot fit the return type and the function name on a single line, break between them.
-
There is always a space between the function name and the open parenthesis.
-
There is always* a space between the parentheses and the parameters.
-
The open curly brace is never at the end of the same line as the last parameter.
Only namespace declarations are allowed to break this rule.
-
The close curly brace is always on the last line by itself.
-
All parameters should be named, with identical names in the declaration and implementation.
-
All parameters are always aligned.
-
Default indentation is 2 spaces.
On one line if it fits; otherwise, wrap arguments at the parenthesis.
Function calls have the following format:
bool retval = do_something ( argument1, argument2, argument3 );
If the arguments do not all fit on one line, they should be broken up onto multiple lines, with each subsequent line aligned with the first argument.
bool retval = do_something (
a_very_very_very_very_long_argument1,
argument2, argument3
);
If the function has many arguments, consider having one per line if this makes the code more readable:
bool retval = do_something (
argument1,
argument2,
argument3,
argument4
);
Format a braced list exactly like you would format a function call in its place.
If the braced list follows a name (e.g. a type or variable name), format as if the {} were the parentheses of a function call with that name. If there is no name, assume a zero-length name.
// Examples of braced init list on a single line.
return { foo, bar };
function_call ( {foo, bar} );
std::pair<int, int> p { foo, bar };
// When you have to wrap.
some_function (
{ "assume a zero-length name before {"},
some_other_function_parameter
);
SomeType variable {
some, other, values,
{ "assume a zero-length name before {"},
SomeOtherType
{
"Very long string requiring the surrounding breaks.",
some, other values
},
SomeOtherType
{
"Slightly shorter string",
some, other, values
}
};
SomeType variable {
"This is too long to fit all in one line"
};
MyType m = {
superlongvariablename1,
superlongvariablename2,
{ short, interior, list },
{
interiorwrappinglist,
interiorwrappinglist2
}
};
[TODO]
[TODO]
No spaces around period or arrow. Pointer operators do not have trailing spaces.
The following are examples of correctly-formatted pointer and reference expressions:
x = *p;
p = &x;
x = r.y;
x = r->y;
Note that:
There are no spaces around the period or arrow when accessing a member. Pointer operators have no space after the * or &.
When declaring a pointer variable or argument, you must place the asterisk adjacent to the type:
char * c; // Bad - spaces on both sides of *
const string & str; // Bad - spaces on both sides of &
char* c; // Good
const string& str; // Good
You should do this consistently within a single file, so, when modifying an existing file, use the style in that file.
[TODO]
Do not needlessly surround the return expression with parentheses. Use parentheses in return expr; only where you would use them in x = expr;.
return result; // Good -- No parentheses in the simple case.
return (
some_long_condition && // Good -- Parentheses ok to make a complex
another_condition // expression more readable.
);
return ( value ); // Bad -- You wouldn't write var = (value);
return ( result ); // Bad -- return is not a function!
[TODO]
The hash mark that starts a preprocessor directive should always be at the beginning of the line.
Even when preprocessor directives are within the body of indented code, the directives should start at the beginning of the line.
// Bad -- indented directives
if (lopsided_score)
{
#if DISASTER_PENDING // Bad -- The "#if" should be at beginning of line
DropEverything();
#endif // Bad -- Do not indent "#endif"
BackToNormal();
}
// Good - directives at beginning of line
if (lopsided_score)
{
#if DISASTER_PENDING // Good -- Starts at beginning of line
DropEverything();
# if NOTIFY // Bad -- Spaces after #
NotifyClient();
# endif
#endif
BackToNormal();
}
Sections in public, protected and private order, each indented two space.
The basic format for a class declaration -- lacking the comments, see [Class Comments](#Class Comments) for a discussion of what comments are needed -- is:
class MyClass:
public InheritedClass
{
public: // two space indent
MyClass ( void ); // four space indent
explicit MyClass ( nom::int32 some_var );
~MyClass ( void ) {}
// Note the blank line here; also, getters should *always* be declared first
nom::int32 some_function ( void ) const;
nom::int32 some_other_function ( void ) const;
// Note the blank line here; setters should *always* be declared last
void set_some_var ( nom::int32 some_var );
void set_some_other_var ( nom::int32 some_other_var );
// Note the blank line here
private: // two space indent
bool some_internal_function ( void ); // four space indent
nom::int32 some_var_;
nom::int32 some_other_var_;
};
Things to note:
-
Any base class name should be on a new line, two tabs from the last position the colon (:)
-
The public:, protected:, and private: keywords should be indented two space.
-
Except for the first instance, these keywords should be preceded by a blank line. This rule is optional in small classes.
-
Do not leave a blank line after these keywords.
-
The public section should be first, followed by the protected and finally the private section.
See [Declaration Order](#Declaration Order) for rules on ordering declarations within each of these sections.
[TODO]
[TODO]
[TODO]
[TODO]
Remove all trailing whitespace. This includes whitespace appearing on a blank line.
You may diverge from the rules when dealing with code that does not conform to this style guide.
Implementation code 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.
- Feel free to base your initial class or function off the provided Template.cpp and Template.hpp files provided under the project root directory.
Prefer using NOM_LOG_INFO and NOM_LOG_ERR for logging output, and NOM_ASSERT instead of assert. See also nomlib/config.hpp.