Jake Warmerdam C++ Style Guide

Background

This is a simple coding standard used for my personal projects. It is not a list of C++ best practices; rather it is a set of naming and formatting conventions intended to produce code that is more readable, maintainable, and attractive.

Naming

Prefixes

Globals, constants, and types use specific prefixes:

General Naming Rules

  • Function names, variable names, and file names should be clear and descriptive.
  • Names should be all lowercase, with words separated by underscores: int example_variable;
  • Simple loop indices can use i,j,k. However, prefer iterator loops to indexed loops.
  • Common abbreviations are also acceptable as part of a name, such as id, ptr, min, max, func.
  • Do not make up new abbreviations.
  • Class members must be prefixed with m_, but this is not required for structs.
  • Use structs to hold simple data. When in doubt, use a class.
  • Some specific standards:
    • Variables and functions dealing with numbers of items in a list should use the word count: int m_player_count;
    • Getters and setters should be prefixed with get_ and set_.
    • Functions that do non-trivial work before returning should start with compute_.
    • Functions that may return nullptr should start with try_to_.
    • Enumeration values should begin with an underscore, with the exception of values that are meant to be markers, in which case they should be named like constants: k_.
static const int k_max_monster_count = 100;
bool g_test_mini_monsters = true;

enum e_monster_flavor
{
    _monster_flavor_a,
    _monster_flavor_b,
};

class c_monster
{
public:
    e_monster_flavor get_flavor() const { return m_flavor; }
    float compute_mass() const;
    const c_monster* try_to_find_enemy() const;

private:
    e_monster_flavor m_flavor;
    h_monster m_spouse;
    int m_eyeball_count;
};

struct s_health_data
{
    float current_health;
    float max_health;
};

File Names

  • File names for class implementations should use the same name as the class, minus any prefix.
  • Other files should follow the naming convention for variables: lowercase, words spaced with underscores.
  • C++ files should end in .cpp and header files should end in .h.

Formatting and Style

Braces and Whitespace

  • Beginning braces occupy their own line. Brace pairs line up vertically.
  • else and else if occupy their own line.
  • Use a space after the keywords if, for, while, and switch.
  • Do not include any extra whitespace between parentheses and the code inside.
  • Always leave a space after a comma, and after semi-colons in for conditions.
  • Use a space before and after the equals sign in assignment.
  • Use a single space around most operators, unless there is a compelling reason not to.
  • Leave a blank line after all code blocks, unless the following line is an ending brace.
  • Leave a blank line after variable declaration groups.
  • Always use braced blocks, even for blocks with a single line.
  • In pointer declarations, the * goes next to the type, not the name: int* my_int;
  • Declare a single variable per line.
  • int compute_range_sum(int first, int last)
    {
        int result = 0;
        
        if (last < first)
        {
            std::swap(first, last);
        }
    
        for (int i = first; i <= last; ++i)
        {
            result += i;
        }
    
        return result;
    }		
    
  • case: blocks should always use braces, and use the following style:
    switch (...)
    {
        case 0:
        {
            //...
            break;
        }
    
        default:
        {
            break;
        }
    }
    
  • Preferred style for ternary operator is:
    (condition)
        ? result_a
        : result_b;
    
  • In function definitions with more than two or three parameters, each parameter should occupy its own line. Consider refactoring the function to take a struct input instead.
  • Function calls with many parameters should use one line per parameter. Parameters can be grouped together on one line if they are logically related, such as a min and max value.

Clarity

  • If there could be any doubt about precedence, use parentheses to clarify. Be liberal, but do not obfuscate the code.
  • Functions should usually have a single return statement.
  • In complex boolean conditionals, prefer defining named variables for each part:
    const bool is_alive = (health > 0);
    const bool is_angry = (get_mood() == _mood_angry);
    
    if (is_alive && is_angry)
    {
        //...
    }
    
  • Variables that are only assigned to once should be marked const.
  • Avoid default arguments.
  • Function overloading is fine if the specific overload being called will be clear at the call site.
  • If a case: block must fall through to the next case:, this must be decorated with fallthrough.
  • Never use the ternary operator twice in one expression.
  • Avoid deep nesting.
  • Avoid "clever" code, which is usually written in the name of efficiency or brevity, at the expense of clarity (and, often, correctness).
  • Always use C++ casts, such as static_cast<>().
  • Use prefix form (++i) of the increment and decrement operators with iterators and other template objects. Avoid all use of postfix form.
  • Use const liberally to clarify intent behind variable, parameter, and function usage. Viral nature of const is considered a good thing.
  • In variable and parameter declarations, place const before the type.
  • Avoid complicated template meta-programming.

Brevity

  • Clarity and brevity usually go hand in hand, but err on the side of clarity.
  • Brevity is for the sake of readability, not reducing typing. Most programming time is not spent typing!
  • Prefer concise focused functions.
  • Functions returning void should omit the unnecessary final return;
  • In loops, break; and continue; are allowed, but discouraged.
  • Use auto to avoid type names that are just clutter. Use type declarations when it helps readability.
  • Use lambda expressions where appropriate.
  • Write all captures explicitly.
  • Be very cautious with macros. Prefer inline functions, enums, and const variables to macros.
  • Try to not #define macros in a .h file.
  • #define macros just before use, and #undef them after.

Local Variables

  • Place a function's variables in the narrowest scope possible, and initialize variables in the declaration.
  • int i;
    i = f();      // Bad -- initialization separate from declaration.
    
    const int j = g();  // Good -- declaration has initialization.
    
    vector<int> v;
    v.push_back(1);  // Prefer initializing using brace initialization.
    v.push_back(2);
    
    vector<int> v = {1, 2};  // Good -- v starts initialized.
    
  • Variables needed for if, while, and for statements should normally be declared within those statements, so that such variables are confined to those scopes:

    while (const char* p = strchr(str, '/'))
    {
        str = p + 1;
    }
    
    if (const c_thing* c = some_func())
    {
        // ... use c;
    }
    

Function Parameters

  • When defining a function, parameter order is: inputs, then outputs.
  • Input parameters are usually values or const references, while output and input/output parameters will be non-const references.
  • Avoid the use of out and in/out parameters when possible. Return a structure instead.

Header Files and Includes

  • In general, every .cpp file should have an associated .h file.
  • Header files should be self-contained and end in .h. Files that are meant for textual inclusion, but are not headers, should end in .inc.
  • All header files should be self-contained. No special conditions should be required in order to include the header.
  • Use #pragma once in header files to prevent multiple inclusion.
  • Order for includes: related header, external headers, project headers.
  • Do not use ./ or ../ in include paths.
  • Within each section the includes should be ordered alphabetically.

Revision 1.0