Style Guide

Introduction

This page serves as a set of guidelines to follow when developing KP3D. I don't think this will be of much use to anybody besides myself, but it's good to make note of what conventions you follow regardless.

C++ Philosophy

C++ is a versatile language, and with the advent of C++11 and C++17, it's become even more so; but it should be noted that there's a time and place to use its features. While KP3D uses C++17, it isn't a requirement to do everything "the modern way."

Files

All header files are named in PascalCase and end with .h. The same applies to translation units as well, except they end with .cpp. All directories and resource files are named in snake_case.

Each project should have a file structure similar to the one below.

📁 KP3D
├── 📁 src
│   ├── 📄 KP3D_Example.h
│   └── 📄 KP3D_Example.cpp
└── 📁 res
    └── 📄 example_resource.txt
📁 Editor
└── 📁 src
    ├── 📄 Ed_Example.h
    └── 📄 Ed_Example.cpp
...

Source files are prefixed by the component name (optionally abbreviated) followed by an underscore.

Headers

Includes

Headers should be organized in the following order, each type separated by a blank line:

For example, in KP3D_Foo.cpp, whose main purpose is to implement KP3D_Foo.h:

#include "KP3D_Foo.h"

#include <stdio.h>
#include <stdlib.h>

#include <string>

#include <barlib/bar.h>

#include "KP3D_Bar.h"
#include "KP3D_Baz.h"

namespace kp3d
{
    ...
}

External libraries use angle brackets while project headers use quotation marks.

Preventing Multiple Inclusions

You can use include guards to prevent multiple inclusions.

#ifndef KP3D_THING_H_
#define KP3D_THING_H_

...

#endif // KP3D_THING_H_

But as an alternative I like #pragma once as it's simpler, cleaner, and you don't have to worry about updating the name should the code ever change (e.g. KP3D_Foo.h gets changed to KP3D_Bar.h and as a result the include guard has to be updated). Not to mention certain IDEs like Visual Studio have a stroke with include guards for some reason.

#pragma once

...

Some people may complain that this isn't supported by all compilers, but if you do just a couple seconds of Googling you'll see that every relevant compiler supports it just fine.

Forward Declarations

Prefer forward declarations to including a header whenever possible, but don't go out of your way to restructure the code so you can use them (e.g. using pointer members instead of object members).

Inline Functions

Define functions inline only when they are small, ~10 lines or fewer. Simple accessors and mutators and math utility functions come to mind. Use __forceinline (or similar) because fuck the police.

Namespaces

Namespaces always follow the snake_case naming style.

namespace kp3d::map_editor
{
    // C++17 nested namespace swag
}

Use nested namespaces judiciously though. Namespaces exist to prevent name clashes, not to organize code. That said, using additional namespaces for utility functions is encouraged and preferred over a class filled with static methods.

Anonymous Namespaces

Prefer anonymous namespaces over static whenever possible.

On using Directives

Avoid this:

#include <iostream>
#include <string>

// If you do this in production code you need to be shot
using namespace std;

int main()
{
    string msg = "dude weed lmao";
    cout << msg << endl;
    
    return 0;
}

Classes & Structs

Classes and structs always follow the PascalCase naming style.

struct Vertex
{
    float x, y, z;
};

Member variables are named in snake_case, with private/protected ones being prefixed with m_ and static ones being prefixed with s_. Private and static uses ms_. Methods always use PascalCase.

class Entity
{
public:
    Entity(float x, float y);
    virtual ~Entity();
    
    void Run();
    
    float GetX() const;
    float GetY() const;
    
private:
    float m_x;
    float m_y;
    
};

Organization

Methods and variables should be separated. Methods may or may not be spaced out.

class FooBar
{
public:
    FooBar();
    ~FooBar();
    
public:
    int foo;
    
private:
    float m_bar;
    
};

Templates

Always use .inl files for templates.

// KP3D_Foo.h
class Foo
{
public:
    template <typename T>
    void DoSomething(T x);

};
// KP3D_Foo.inl
template <typename T>
void Foo::DoSomething(T x)
{
    ...
}

Class vs. Struct

Structs are used exclusively as POD. Classes are not.

Comments

Block comments use the typical style. Only use them where necessary though.

/*
 * Says something!
 */
void SaySomething(const std::string& something)
{
    std::cout << something << std::endl;
}

Referencing Code

Always use a grave accent (`, like markdown) to reference code (e.g. `SomeFunc()`)

Single-line Comments

Single-line comments may be used for describing what's going on inside a function, explain preprocessor directives, sum up what a class/struct is, describe members, etc. These are used very sparingly.

Refrain from doing this (unless it isn't self-explanatory):

// Here's a player
class Player
{
public:
    int health;            // Useless comment explaining what the name already suggests
    int ammo;              // This is ammo btw incase you couldn't tell
    std::vector<Wep> weps; // The player's weapons
    
};

Prefer this instead:

class Player
{
public:
    // Stats
    int health;
    int ammo;
    
    // Possessions
    std::vector<Wep> weps;
    
};

Keep Lines a Reasonable Length

Try to keep your lines under 120 characters long. 130 is the point GitHub overflows. But even if you're not near the limit, you should still break up your code if it improves readability.

// Bad, hard to follow
if (foo_position.x >= 0 && foo_position.y >= 0 && foo_position.x <= 5 && foo_position.y <= 5)
{
}

// Good, easier to read
if (foo_position.x >= 0 &&
    foo_position.y >= 0 &&
    foo_position.x <= 5 &&
    foo_position.y <= 5)
{
}

Use Initializer Lists

For POD types, the performance gains of an initializer list are the same as manual initialization, but for other types there is a clear performance gain, see below.

// True neutral
class Foo
{
public:
    Foo(int bar)
    {
        m_bar = bar;
    }

private:
    int m_bar;

};

// Chaotic neutral
class Foo
{
public:
    Foo(Bar bar)
    {
        m_bar = bar;
    }

private:
    Bar m_bar;

};

// Neutral good, no performance gains but the code is cleaner
class Foo
{
public:
    Foo(int bar):
        m_bar(bar)
    {
    }

private:
    int m_bar;

};

// Lawful good, default constructor for `m_bar` is never called,
// so there is a performance gain if `Bar` is not
// `std::is_trivially_default_constructible`
class Foo
{
public:
    Foo(Bar bar):
    	m_bar(bar)
    {
    }

private:
    Bar m_bar;

};

Use the Correct Integer Type for STL Features

STL generally uses size_t. The size of it varies by implementation.

Don't use auto

This might be controversial with the modern C++ illuminati but I'm not really into prioritizing typing speed over readability. Ugly doesn't mean unreadable. Exceptions may be made if the type is glaringly obvious, but try to avoid it.

Use const Where Possible

const tells the compiler that a variable or method is immutable. This helps the compiler optimize the code and helps the developer know if a function has a side effect. Also, using const & prevents the compiler from copying data unnecessarily. Small structs do not always have to be passed by reference, but it's a good practice.

// Bad
void DoSomething(std::string bar)
{
    ...
}

// Good
void DoSomething(const std::string& bar)
{
    ...
}

Avoid Implicit Conversions

Single Parameter Constructors

Single parameter constructors can be applied at compile time to automatically convert between types. This is handy for things like std::string(const char*) but should be avoided in general because they can add to accidental runtime overhead. Instead mark single parameter constructors as explicit, which requires them to be explicitly called.

Conversion Operators

Similarly to single parameter constructors, conversion operators can be called by the compiler and introduce unexpected overhead. They should also be marked as explicit.

// Bad
class Thing
{
public:
    operator int()
    {
        return 2;
    }

};

// Good
class Thing
{
public:
    explicit operator int()
    {
        return 2;
    }

};

Consider the Rule of Zero

The Rule of Zero states that you do not provide any of the functions that the compiler can provide (copy constructor, copy assignment operator, move constructor, move assignment operator, destructor) unless the class you are constructing does some novel form of ownership.

The goal is to let the compiler provide optimal versions that are automatically maintained when more member variables are added.

GLSL

All the conventions in this document apply to GLSL as well, but with a few exceptions:

Separate Methods and Fields Using Access Modifiers

In C++, you can repeat the public, protected, and private access modifiers in class definitions. This can be used to make classes appear cleaner by separating methods and fields. For example:

class Thing
{
public:
    void DoSomething();

public:
    int x;

private:
    void DoSomethingElse();

private:
    int m_y;

};

The Nitpicks

Colon Cancer

Any time you see a colon next to anything except the ternary operator, chances are it doesn't need a space to the left of it. Visual Studio will often fight you when you try to correct this, but it's my personal preference and Microsoft can go fuck themselves.

// Broke
class Thing : ThingBase
{
public:
    Thing() :
        ThingBase()
    {
        for (const Object& i : m_objects)
            DoSomething(i);
    }

};

// Woke
class Thing: ThingBase
{
public:
    Thing():
        ThingBase()
    {
        for (const Object& i: m_objects)
            DoSomething(i);
    }

};

Switch Statements

The CIA wants you to think it's just if/else, it's not. It's a jump. Format it like this to open a new scope:

void RIPTerry(int x)
{
    switch (x)
    {
    case 0:
    	std::cout << "dude weed lmao" << std::endl;
    	break;
    case 1:
    	{
            std::string msg = "lmao weed dude";
            std::cout << msg << std::endl;
    	}
    	break;
    }
}

Tabs vs. Spaces

Use tabs for indentation and spaces for extra formatting/alignment.

Pointer/Reference Alignment

Asterisks and ampersands always go on the left.

Function Calls Requiring Multiple Lines

If a function has enough parameters to warrant breaking the call into multiple lines, format them like this:

void DoSomething()
{
    ReallyLongFunction(
        4, 2, 0, 6, 9,                  // If numbers are related, group them.
        1,
        2,                              // Otherwise, make more lines.
        "really long string",
        REALLY_LONG_MACRO,
        ThisIsA(
            "really long function call"
        )
    );
    
    ...
}

If the first/only parameter entails a long function call, initializer list, or lambda, it needs a new line as well:

void DoSomething()
{
    // Bad, easy to confuse arguments
    Foo(ReallyLongFunction(
        ...
        ...
        ...
    ));
    
    Foo([](){
        ...
        ...
        ...
    });
    
    Foo({
        0, 1, 2
    });
    
    // Good, clear distinction between `Foo` and its arguments
    Foo(
        ReallyLongFunction(
            ...
            ...
            ...
        )
    );
    
    Foo(
        []()
        {
            ...
            ...
            ...
        }
    );
    
    Foo(
        {
            0, 1, 2
        }
    );

    ...
}

At a glance, it's easier to see what got passed in using the latter. Ugly != unreadable.

Operator Overloading

Operator overloading should be used with caution. I'm not super strict about its use, but I prefer it kept to things like vector math where the intent of each overload is obvious.

Use private Explicitly

By default, class members and parents are private. Regardless, you should still use the keyword.

// Bad
class Thing: ThingBase
{
    int m_x;
	
public:
    Thing();

};

// Good
class Thing: private ThingBase
{
public:
    Thing();

private:
    int m_x;

};

Closing Notes

Shoutouts to the poor bastard who had to write the giant Google C++ Style Guide I took cues from. All in all just be consistent. Google put it best: "If you are editing code, take a few minutes to look at the code around you and determine its style. If they use spaces around their if clauses, you should, too. If their comments have little boxes of stars around them, make your comments have little boxes of stars around them too."

I'll probably add more to this page down the road, but I think for now it's a good foundation.