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.
Single responsibility principle
Adhere to making your modules (files, classes, and functions) be responsible for or do just one thing.
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;
}
}
Long lines
Do not write more than 100 characters per line of code.
// Don't do this.
int really_long_function(std::vector<std::pair<std::string, double>> arg1, std::vector<std::vector<double>> arg2, std::string arg3);
// Do this.
int really_long_function(std::vector<std::pair<std::string, double>> arg1,
std::vector<std::vector<double>> arg2,
std::string arg3);
// Or do this.
int really_long_function(
std::vector<std::pair<std::string, double>> arg1,
std::vector<std::vector<double>> arg2,
std::string arg3
);
// Or do this.
using map_t = std::vector<std::pair<std::string, double>>;
using matrix_t = std::vector<std::vector<double>>;
int really_long_function(map_t arg1, matrix_t arg2, std::string arg3);
Another example for classes:
// Don't do this.
class ClassWithLongConstructor {
ClassWithLongConstructor(std::map<std::string, double> const& relations, std::vector<std::string> const& names) : mRelations(relations), mNames(names) {}
};
// Do this.
class ClassWithLongConstructor {
ClassWithLongConstructor(std::map<std::string, double> const& relations,
std::vector<std::string> const& names)
: mRelations(relations),
mNames(names) {}
};
// Or do this.
class ClassWithLongConstructor {
ClassWithLongConstructor(
std::map<std::string, double> const& relations,
std::vector<std::string> const& names
)
: mRelations(relations),
mNames(names) {}
};
There are many more different styles of breaking up long lines. You can use those as long as you’re consistent.
Enumerations
Prefer scoped enums over unscoped enums.
enum PoorEnum {
A,
B,
};
enum class GoodEnum {
A,
B,
};
Excessive Comments
Do not excessively comment every line. Comment blocks of code, or lines of code if it is not immediately understandable/readable.
// Don't do this.
int var{3}; // Create var variable.
int input{}; // Create input variable.
std::cin >> input; // Read input from stdin.
var *= input; // Multiply var and input.
std::cout << var << std::endl; // Print var.
// Do this.
// Multiply input with var.
int var{3};
int input{};
std::cin >> input;
var *= input;
std::cout << var << std::endl;
Recommended
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 << ")";
}
Helper Functions
Place helper functions in the scope of where they are used.
// Do this if helper does not depend on MyClass.
void helper();
class MyClass {
private:
// Do this if helper is only ever used in MyClass.
void helper();
};