Skip to content

Style Guide

C++ Style

SJSU-Dev2 follows the Google C++ style guide with exception to the subjects stated here. See: http://google.github.io/styleguide/cppguide.html

Usage of C files

Only on special cases should .c (C-only) source files be used. When ever possible, be sure to use C++ rather than C.

Including C headers

Always opt to use the C++ version of the standard library headers. For example, besides including <stdlib.h> use <cstdlib>.

C++ Standard Libraries

Many of the C++ libraries are large and complex and not optimal for the SJSU-Dev2 project. For the most part stay away from using or including std C++ libraries. For example, simply the simple #include <iostream> will grow the executable's size by 150kB!? Bare in mind that the flash size for the board is 512kB. <iostream> will take up approximately 30% of your flash size when it is included and not even used.

  • Here are a few standard C++ libraries you can include:

    • <variant>
    • <utility>
    • <tuple>
    • <type_traits>
    • <limits>
    • <bitset>
    • <atomic>
    • <utility>
    • <algorithm>
    • <c*> C standard libraries
  • C++ libraries that increase compile time and should not be included:

    • <functional>
    • <chrono>
  • C++ libraries that increase binary size and you should NEVER be included:

    • <iostream>

Taking more than 4 arguments for a function

If a method or function takes more than 4 arguments, consider taking a structure as an input rather than the seperate arguments.

Without structure parameter:

void GeneratePulse(uint32_t frequency, uint32_t amplitude, uint32_t cycles, bool rise_in_volume);

// Used like:
GeneratePulse(2000, 5, 100, false);

/// OR

struct GeneratePulseParameter
{
  uint32_t frequency;
  uint32_t amplitude;
  uint32_t cycles;
  bool rise_in_volume;
};
void GeneratePulse(GeneratePulseParameter parameters);

// Used like:
GeneratePulse(2000, 5, 100, false);

Refrain from abbreviations

Choose the variable name "timer" over the name "tim". Choose InterruptHandler over IHandler or IntHandler. FunctionPointer over FuncPtr. Be expressive with your variables and make sure it is obvious what your variable does.

Exceptions are abbreviations known in the industry such as using int i within a for loop. Another is the use of abbreviations like SPI and I2C where most people do not typically remember what the actual words for these abbreviations, but understand how these protocols work.

Code expressions read nicely from left to right

When reading code, we read from left to right as we do any text. When writing APIs think about how nicely your code reads from left to right.

Link to how this should look: Left to Right Technique.

Curly Braces

SJSU-Dev2 will follow the Allman style of curly braces, where each curly brace gets their own line.

Infinite Loops

Use Halt(), found in the library/utility/ folder rather than for(;;), or while (true) etc...

Include Guards

The first non-comment line of every header file must be #pragma once. DO NOT use the traditional macro include guards.

Memory Allocation

SJSU-Dev2 code may NOT use dynamic memory such as with malloc or new. All memory must be preallocated either as stack memory or via by global storage. Code must NOT use C++ libraries that allocate memory, such as std::string.

Pointers and references

Pointer and reference symbols (asterisks and ampersand), must be in the center of type and the variable name.

For example: char * array; and PositronManager & pos;

Prefer storing references over storing pointers. Use pointers only for cases where the pointer's target may be changed or needs to point to nullptr. References passed to functions must be passed as const.

Using Preprocessor macro

Stay away from #define as much as possible. Use constexpr and inline in place of macros.

If a macro solution is the most optimal solution, they may be used them, but they must be prefixed with SJ2_ or _SJ2_. When prefixed with a _, that means that this macro is an internal macro and should not be called used directly by application code.

Usage of constexpr

Prefer to use these whenever possible to do complex computations during compile time rather than during runtime.

Preprocessor Semicolons

Macros must force the user to end them with a semicolon. Thus the contents of the macro must be wrapped with do { ... } while(0) loop.

Standalone macros must end with a throwaway static_assert statement.

#define _SJ2_GENERATE_SOMETHING(name)     \
    bool factory_##name() { return val; } \
    static_assert(true)

Preprocessor Conditionals

Prefer to use #if rather than #ifdef. #if checks for definition as well as if the expression evaluates to something truthful.

Every #endif must be commented with the exprssion used in the #if statement.

Integer typing

Prefer to use the sign and width designated <cstdint> integers. Prefer int32_t a = 5; compared to int a = 5;.

Low Level Driver Constructors

Drivers must not use their constructors to initialize hardware peripherals as the order in which constructors are executed is undefined.

Each driver must contain an Initialize(...) method that the developer must explicitly call to manipulate hardware.

This is to ensure that the system and C++ libraries have been initialized before modifying hardware registers.

The constructor should be used to initialize class memory such as member variables nothing more.

File comments

The top of each file must include a comment explaining the purpose of the file.

Comment style

Prefer that comments be directly above the line it means to comment.

Use // ... for all comments in the code except for in macros for which // cannot be used, thus /* ... */ is acceptable only in this case.

/// must be used for doxygen documentation comments.

Assertive Software

Software in SJSU-Dev2 should be as assertive, in that you should use the SJ2_ASSERT_FATAL(...) or SJ2_ASSERT_WARNING(...) at run time to either prevent or warn the developer about potentially dangerous operations. compile time error function whenever the user performs and illegal action. For example, lets say the developer put 0Hz for the constructor of the SystemClock class. There should be a SJ2_ASSERT_FATAL() check done the parameters to make sure that the value is within a reasonable range.

If possible, prefer the compile time static_assert(), since this can tell the user about a bug well before they have flashed their board.

class SystemClock
{
    constexpr SystemClock(uint32_t frequency)
    {
        SJ2_ASSERT_FATAL(1 <= frequency && frequency <= 100000000,
            "SystemClock frequency must be between 1 and 100000000");
    }
}

File formatting

Every file must end with a newline character.

Number formatting

Never use decimal or octal when doing bitwise operations. You may use hex 0x32, or binary 0b0011'0010.

Please DO use the single quote ' to separate your numbers for example: 0b0110'1000'0101'1110 and 12'000'000.

Making the number segments more visible if there are obvious mistakes.

Make sure that the radix of the number you are using from datasheets or manuals matching the radix in those manuals. If the datahsheet says that at address 0xABCD, you should also use 0xABCD rather then converting it to 43981.

Returning values

Prefer to only have 1 return statement within function and to have it return a result variable.

int function()
{
  // Prel

Returning values

Prefer to only have 1 return statement within function and to have it return a result variable.

int function()
{
  // Preload with a default return.
  int result = 0;
  //
  // Do some stuff in the function that any alter the result.
  //
  return result;
}

How to Efficiently Return variables

int function(bool check1, bool check2)
{
  // Preload with a default return.
  int result = 1;
  if (check1 && check2)
  {
    result = 17;
  }
  else if (!check1 && check2)
  {
    result = 17;
  }
  if (check1 && !check2)
  {
    result = 17;
  }
  // As you can see, using 1 as the preloaded value was a poor choice because
  // we had to write 17, 3 times and the compiler may have had to write those
  // three assignments into assembly.
  return result;
}

int functionBetter(bool check1, bool check2)
{
  // A better alternative would be set the default value to 17, now we only
  // have to check 1 case, and all other are taken care of.
  int result = 17;
  if (!check1 && !check2)
  {
    result = 1;
  }
  return result;
}

Use third party library "units" whenever appropriate

In order to handle unit conversions and to make passing values with an associated unit attached to it easier use the units library. See: https://github.com/nholthaus/units on how to use it.

Prefer an interface like:

#include "utility/units.hpp"

class DistanceSensor
{
 public:
  virtual Status Initialize() const = 0;
  virtual Status GetDistance(units::length::millimeters_t * distance) const = 0;
  // This can stay a float from 0.0f <-> 1.0f because some datasheets do not
  // assign units to the strength, and keep it as a percentage.
  virtual Status GetSignalStrengthPercent(float * strength) const = 0;
};

Over the following that requires you to handle the unit conversions yourself. If every engineer does this, there are is bound to mistakes and worse, we bloat the code doing the same operations but in different locations:

class DistanceSensor
{
 public:
  static constexpr float kConversionCM   = 10;
  static constexpr float kConversionInch = 25.4f;
  static constexpr float kConversionFt   = 304.8f;

  // ==============================
  // Required Methods for Subclasses
  // ==============================
  virtual Status Initialize() const = 0;
  // Distance units are to be in milimeters
  virtual Status GetDistance(uint32_t * distance) const           = 0;
  virtual Status GetSignalStrengthPercent(float * strength) const = 0;

  // ===============
  // Utility Methods
  // ===============
  Status GetDistanceCm(float * distance) const
  {
    uint32_t sensor_reading;
    Status sensor_status;
    sensor_status = GetDistance(&sensor_reading);
    *distance     = static_cast<float>(sensor_reading / kConversionCM);
    return sensor_status;
  }
  Status GetDistanceInch(float * distance) const
  {
    uint32_t sensor_reading;
    Status sensor_status;
    sensor_status = GetDistance(&sensor_reading);
    *distance     = static_cast<float>(sensor_reading / kConversionInch);
    return sensor_status;
  }
  Status GetDistanceFt(float * distance) const
  {
    uint32_t sensor_reading;
    Status sensor_status;
    sensor_status = GetDistance(&sensor_reading);
    *distance     = static_cast<float>(sensor_reading / kConversionFt);
    return sensor_status;
  }
};