← Back to 24T3

Style Guide

Official style guide for MTRN2500.


This style guide contains both good C++ and general programming practices.

Coding practices

These are general coding practices that should be followed for all code written throughout this course and more generally in the future.

Don’t repeat yourself (DRY)

Do not repeat the same code over-and-over again in separate locations. Rather, break it up into logical functions that can be called with appropriate parameters. Your code should be clean, modular, maintainable, and reusable.

Keep it simple (KISS)

KISS prefers solutions that are more straightforward and do not indroduce unnecessary complexity. This can be done by using the most appropriate coding statements and appropriate functions. Your code should be able to describe its functionality. By writing code that folows KISS it is simpler, clearer, and easier to understand.

Write consistent code

When writing code, you should have a consistent style throughout everything you write. This includes using the same casing for your variables (snake case, pascal case, camel case), and always having the same spacing and brace locations.

Appropriate use of comments

When writing code, you should use comments meaningfully to describe what the code is trying to achieve. This both includes adding sufficient comments to make your code easy to understand and read easily as well as not overloading the code with too many comments that clutter it up.

Prefer modern coding practices

You should be using modern C++ coding principles when writing your code. This includes concepts such as using smart pointers over raw pointers, and the use of appropriate C++ algorithms.

Required

This section of the style guide contains styles that we explicitly ask you to follow. The required style will be referred to when marking for style in assessments.

Naming

Variable names should be meaningful, concise, and describe things.

// Don't do this. double lfsv{42.0}; double rbsv{42.0}; // Names are not descriptive. // No leading underscores int __var{42}; // Do this. double left_front_sensor{42.0}; double right_back_sensor{42.0};

Function names should be meaningful, concise, and describe actions.

// Don't do this. int double_the_value_and_return_it(int val); int d(int val); // Do this. int make_double(int val);

Arrays

Avoid C-style arrays. Prefer std::array or std::vector instead.

// Don't do this. int bad_array[100]; // Do this. std::array<int, 100> good_array{}; std::vector<int> good_vector{};

Functions

Minimise code duplication with functions when possible.

// Don't do this. int a{0}; int b{0}; { a *= 5; b *= 4; } { b *= 5; a *= 4; } // Do this. void special_multiply(int& arg1, int& arg2) { arg1 *= 5; arg2 *= 4; } int a{0}; int b{0}; special_multiply(a, b); special_multiply(b, a);

Nested Statements

Do not have deep-nested statements, use functions to extract logic. Rule-of-thumb is at most 3 nested statements.

// Don't do this. while (...) { while (...) { while (...) { if (...) { } } if (...) { } else { } } } // Do this. void foo2(...) { while (...) { if (...) { } } if (...) { } else { } } void foo1(...) { while (...) { while (...) { foo2(...); } } }

Memory Allocation

Avoid malloc and free. Prefer new and delete.

// Don't do this. int* bad_ptr = (int*)malloc(sizeof(int)); free(bad_ptr); // Do this. int* better_ptr = new int; delete better_ptr;

For array pointers:

// Don't do this. int* bad_array_ptr = (int*)malloc(sizeof(int[10])); free(bad_array_ptr); // Do this. int* better_array_ptr = new int[10]; delete[] better_array_ptr;

Void Pointer

Never use void*.

// Don't do this. void* can_be_anything;

Pointer Comparison

Do not use NULL for pointer comparison. Prefer nullptr.

int* ptr; // Don't do this. if (ptr == NULL) { ... } // Do this. if (ptr == nullptr) { ... }

Casting

Avoid C-style and implicit casts. Prefer C++-style and explicit casts.

int original{42}; // Don't do this. double implicit_cast{original}; // Implicit cast. double bad_cast{(double)original}; // C-style cast. // Do this. double good_cast{static_cast<double>(original)}; // Explicit and C++-style.

Grouped Includes

Includes should be grouped into blocks in order of inclusion dependency.

// Don't do this. #include <algorithm> #include "a_utilities.hpp" #include "b_utilities.hpp" #include "c_utilities.hpp" #include <iostream> #include <vector> #include <webots/Motor.hpp> #include <webots/Robot.hpp> // Do this. #include <algorithm> #include <iostream> #include <vector> #include <webots/Motor.hpp> #include <webots/Robot.hpp> #include "a_utilities.hpp" #include "b_utilities.hpp" #include "c_utilities.hpp"

Header Guards

Header guards must be used.

// At top of hpp #ifndef MY_FILE_HPP #define MY_FILE_HPP // Write your code here. // At bottom of hpp. #endif

Alternatively, #pragma once is allowed:

// At the top of hpp. #pragma once // Write your code here.

Using Namespace

Never use using namespace. Write the variable with its scope.

// Don't do this. using namespace std; using namespace webots; cout << "Hello"; Robot robot{}; // Do this. std::cout << "Hello"; webots::Robot robot{};

Constant Variables

Do not use C-style constants. Use const or constexpr for global constant variables.

// Don't do this. #define SIZE 1234; // Do this. constexpr int SIZE{1234};

Use const or constexpr when possible.

// Don't do this. int a{3}; for (int i{0}; i < 10; i++) { int b{a * 2}; } // Do this. int const a{3}; for (int i{0}; i < 10; i++) { int const b{a * 2}; }

Loops

Use loops appropriately for different contexts.

// Prefer index-based for-loops when looping through sequential random-access containers and the index is required. for (int i = 0; i < vec.size(); i += 2) { do_something_with_index_and_value(i, vec[i]); } // Prefer iterator for-loops when looping through STL containers and the iterator is required. for (auto it = vec.begin(); it != vec.end(); it++) { do_something_with_iterator_and_value(it, *it); } // Prefer range-based for-loops when iterating through all indexes in the STL container and only the values are needed. for (auto const& i : vec) { do_something_with_value(i); } // Prefer STL algorithms over all other for-loops when possible (unless it hinders readability). std::copy(vec.begin(), vec.end(), dst.begin());

Constant Methods

Use const methods where you can.

struct MyClass { // Don't do this. int getVar() { return var; } // Do this. int getVar() const { return var; } int var{0}; };

Defining a non-const reference-return method should be paired with a const version.

struct MyClass() { // Don't do this. int& operator[](int index) { return elements[index]; } // Do this. int& operator[](int index) { // Non-const version. return elements[index]; } int operator[](int index) const { // Const version. return elements[index]; } std::vector<int> elements; };

Passing

Prefer to pass non-primitive types (e.g. classes and structs) by reference to avoid creating a copy of the object.

class MyClass; // Don't do this. void foo(int val, MyClass obj); void foo(int val, MyClass const obj); // Do this. void foo(int val, MyClass& obj); // Even better. void foo(int val, MyClass const& obj);

Smart Pointers

Avoid using new and delete when possible.

Prefer smart pointers instead.

// Don't do this. int* bad_ptr = new int; delete bad_ptr; // Better. std::unique_ptr<int> managed_unique_ptr{new int}; std::shared_ptr<int> managed_shared_ptr{new int}; // Even better. std::unique_ptr<int> better_managed_unique_ptr{std::make_unique(0)}; std::unique_ptr<int> better_managed_shared_ptr{std::make_shared(0)};

Move Semantics

Applying move semantics should invalidate the moved-from object’s resources.

// Don't do this. class Vector { public: Vector(Vector&& v) { vec = std::move(v.vec); } private: std::vector<int> vec; }; // Do this. class Vector { public: Vector(Vector&& v) { vec = std::move(v.vec); v.vec = {}; // Invalidate v's resources. } private: std::vector<int> vec; };

Class Access Specifiers

Variables and methods that we do not want the user to use should be marked private.

class Number { // Don't do this. public: Number sqrt(Number num); Number help_sqrt(Number num); int internal_num; // Do this. public: Number sqrt(Number num); private: Number help_sqrt(Number num); int internal_num; };

Assignment Operators

Assignment operators must always return *this.

struct MyClass { // Don't do this. void operator=(MyClass const& rhs) { var = rhs.var; } // Don't do this either. MyClass& operator=(MyClass const& rhs) { var = rhs.var; return rhs; } // Do this. MyClass& operator=(MyClass const& rhs) { var = rhs.var; return *this; } int var; };

Consistency

Be consistent with number of spaces, or using spaces vs tabs when indenting.

// Don't do this. if (...) { if (...) { int a; } // There is a tab character hidden here. } // Do this. if (...) { if (...) { int a; } }

Be consistent with where braces start.

// Don't do this. if (...) { if (...) { int a; } } // Do this. if (...) { if (...) { int a; } } // Or do this. if (...) { if (...) { int a; } }

Be consistent with naming style. Don’t mix it up.

// Don't do this. int my_var; int mySecondVar; // Do this. int my_var; int my_second_var; // Or do this. int myVar; int mySecondVar;

Be consistent with spacing.

// Don't do this. for (int i=0;i<45;i++){ if(i> 0) { int a; } } // Do this. for (int i = 0; i < 45; i++) { if (i > 0) { int a; } }

Enumerations

Prefer scoped enums over unscoped enums.

enum PoorEnum { A, B, }; enum class GoodEnum { A, B, };

This section of the style guide contains styles that we recommend to you. It will not be used to assess you except that you are consistent with the style you have chosen.

Suggested Naming

Classes and class files uses PascalCase.

// MyClass.hpp or MyClass.cpp class MyClass { }

Member variables are prefixed with m and uses camelCase.

class MyClass { int mSize; std::vector<int> mElements; }

Non-member variable names should use camelCase.

class MyClass { void foo() { int myVar1; // Non-member variables. int myVar2; } }

Initialisation

Prefer initialisation over assignment when possible.

// Don't do this. int var = 42; std::vector<int> vec = {1, 2, 3, 4}; // Do this. int var{42}; std::vector<int> vec{1, 2, 3, 4};

Suggested Bracing

Braces start on the same line.

void foo() { } int main() { if (...) { while (...) { } } }

Sorted Includes

Includes should be lexicographically ordered.

// Don't do this. #include "c_utilities.hpp" #include "b_utilities.hpp" #include "a_utilities.hpp" // Do this. #include "a_utilities.hpp" #include "b_utilities.hpp" #include "c_utilities.hpp"

Overloaded Functions

Overloaded functions must have the same expected behaviour.

// Don't do this. void print_with_brackets(int var) { std::cout << "(" << var << ")"; } void print_with_brackets(double var) { std::cout << "<" << var << ">"; // Did not expect different brackets. } // Do this. void print_with_brackets(int var) { std::cout << "(" << var << ")"; } void print_with_brackets(double var) { std::cout << "(" << var << ")"; }