Workspace 7.0.2
Coding Standard

Introduction

This document describes the coding standard used by the Workspace developers. It reflects the desired style and conventions to be used throughout the Workspace code base. Older code may not yet adhere to the advice given here, so such cases should be brought into compliance as developers encounter them where practical. For the most part, this standard addresses C and C++ code, but some items are applicable more broadly to other languages, tools or formats used or supported by the build system.

We welcome other groups adopting these coding standards for their own work. In particular, we encourage Workspace plugin developers to consider following these conventions so that others using their work will have a certain level of familiarity with the overall style.

General
Names
Headers
Comments
Formatting
Implementation

References


General

Spelling conventions

  • Use US English for all source code, documentation, file and directory names.
  • If you want to support Australian English in a user interface, use a Qt translation file.

Names

  • In general, avoid using abbreviations except for very well-known cases (eg min, max, etc.).
  • Try to avoid carrying across poor names from other code bases (especially variables). Instead, include a comment where the entity is defined to indicate the old name.
  • A few special abbreviations are used by the Workspace code base because they occur very frequently and they prevent overly verbose code:
    • for loop counters are frequently named i, j or k
    • Loop iterators are often abbreviated to iter (this differentiates them from ordinary integer counters such as i).
    • File and class names use impl instead of implementation.

Namespaces and classes

  • Names must contain only letters and numbers (note: no underscores).
  • Each word within the name should start with an uppercase letter, including the start of the name.
  • All other characters should be lowercase, even for acronyms.
  • As a special exception, namespace names which are a single acronym may have all characters in uppercase (eg CSIRO).
Correct Incorrect
SomeProgClass Some_Prog_Class
someProgClass
Someprogclass
VtkWriter VTKWriter
Application APPLICATION
  • Members of "Impl" classes must have names with trailing underscores
Correct Incorrect
rectangle_ rectangle
executionMode_ executionMode
ExecutionMode
execution_mode

Functions and macros

  • The rules for function names are the same as for class names, except that function names must begin with a lowercase letter.
  • Function parameter names should follow the rules for variables.
  • Names of function-like macros should follow the same rules as functions.
  • Do not use prototypes where the parameters are specified with a type only, include meaningful names.
  • If a parameter type is a reference or pointer, the trailing & or * should be placed with the type, not the variable name.
  • If a parameter is being passed by value, do not include a const specifier
Note
It is meaningless as far as the function signature is concerned to have a const specifier on a function parameter passed by value. The compiler silently ignores it when matching calls to the function.
Correct Incorrect
getBigThings() GetBigThings()
get_big_things()
getbigthings()
GETBIGTHINGS()
getBigThings(int key, QString value) getBigThings(int, QString)
getBigThings(int numItems) getBigThings(int n)
getBigThings(double* ptr) getBigThings(double *ptr)
getBigThings(int numItems) getBigThings(const int numItems)

Variables

  • Non-member variables (local, global, file-scope statics and function parameters) have the same name rules as function names.
  • Member variables (static or non-static) have the same name rules as function names with the following variations/restrictions:
    • The variable name must end with a trailing underscore if it is private or protected.
    • If the variable is public, the underscore should be omitted.
    • If the class has only public variables and no member or static functions, the trailing underscores should be omitted.
    • Do not use a prefix such as m_ on the name.
    • Avoid using ptr or similar in the name for member variables that hold pointers.
  • For const references, the const keyword should be placed before the type, as in const QString& someVar.
  • For pointers to const objects, the placement of const is similar to that of references, ie const char* someVar.
  • For a const pointer to something (which is much less common), the const keyword has to be placed between the * and the variable name, as in char* const someVar.
Note
For an explanation of the differences between the various const pointer types, see the C++ FAQ at http://www.parashift.com/c++-faq-lite/const-correctness.html#faq-18.5

Enums

The rules for enums are the same as for class names. This applies to both the enum name (ie its type identifier) and to the names of its values (ie its enumerators).

Typedefs and other types

The rules for typedefs are the same as those of the entity they are typedef'ing. For example, a typdef for a class should conform to class name rules.

XML tags

The rules for XML tags are the same as for class names, except that all characters should be in lowercase.

File system

This section covers naming items in the file system within the source tree. It is not necessarily meant to be applied to files created or used by the software at run-time, although the rules specified here would often be a useful guideline.

Directories

  • Prefer to name your directories in the same form as you would class names (ie begin with a capital letter and use camel-case thereafter).
  • For certain specific cases where there is already a long-established convention (eg bin, lib, doc and so on), the long-accepted name can be used, but this should be rare and is not preferred.

Files

  • Names of files used as part of the build should consist of only lowercase letters and numbers.
  • File names should always start with a letter.
  • Underscores should not be used in source file names except for the following special cases:
    • Test source files should follow the naming convention of the form test_xxxx.cpp (see Tests).
    • Source files may append an underscore followed by a platform-specific suffix on the base name of the file.
    • Images, XML files and other non-source files may use underscores if necessary, but prefer to avoid underscores for consistency with source files where possible.
Correct Incorrect
somesourcefile.cpp some_source_file.cpp
someSourceFile.cpp
file1.cpp 1file.cpp
file_1.cpp
test_foobar.cpp testfoobar.cpp
foobar_test.cpp
foobartest.cpp
foobar_win32.cpp
foobar_mac.cpp
foobar_linux.cpp
foobarwin32.cpp
mac_foobar.cpp
foobar-linux.cpp
  • File name extensions should not be more than three characters long and should be lowercase.
  • Exceptions can be made for long-established conventions such as .html
  • Use familiar file name extensions and adhere to the following conventions:
File Type Extension
C source .c
C++ source .cpp
C/C++ header .h
C/C++ source with doxygen comments only .dox
Qt Designer UI file .ui
Qt resource file .qrc
HTML source .html
JPEG image .jpg

Tests

  • Most C++ tests will be defined in a file named test_xxx.cpp where xxx is the name of the test and would follow the same rules as an ordinary C++ source file name.
  • For C++ tests spanning multiple source files, the test file that contains main() should be named test_xxx.cpp as above. Other source files follow the usual rules for source files.
  • Tests written in other languages are encouraged to follow a similar naming structure as for C++.
Correct Incorrect
test_foobar.cpp testfoobar.cpp
foobar_test.cpp
foobartest.cpp

Pre-processor defines

  • Where possible, pre-processor defines should be avoided in favour of const variables or enums, since those offer much better type safety than a #define.
  • Inlined functions should also be preferred over #define function macros for similar reasons.
  • Where a #define is used to define a value, the name of the symbol being defined should consist only of uppercase letters, numbers and underscores.
  • Where the name of a #define contains more than one word, underscores can be used to separate the words for readability.
  • Where a #define is used to define a function macro, the name of the function macro and the names of the function macro's parameters should follow the same rules as for an ordinary function.

Headers

Header guards

  • All header files must have a header guard defined, whether it is expected that the header will be included by multiple files or not. This also applies to ...impl.h headers.
  • The name used for a header guard should be to take the fully scoped name of the class defined in that header, convert all names to uppercase and scoping operators to an underscore, then append _H.
  • For the cases where a header defines more than one class (which is strongly discouraged), use the main class defined by the header to form the header guard name.
  • Where a header provides no classes, some other substitute should be used for the class name, such as a function or type that the header provides (including the namespaces, if present).

The standard form of a header guard is as follows:

// Copyright header would be included first....

#ifndef CSIRO_GUMBY_FOOBAR_H
#define CSIRO_GUMBY_FOOBAR_H

namespace CSIRO
{
namespace Gumby
{
    class FooBar
    {
        // ...
    };
}}

#endif

Forward declarations

In header files, use forward declarations rather than a full #include if:

  • Nothing in the header requires more than a forward declaration.
  • A developer would not intuitively expect the full header to have been included.

Header ordering and placement

  • Use the following header inclusion order:
    • Standard C/C++ headers
    • Qt headers
    • Your own headers
  • Place headers before all code.
  • Do not place a #define before headers, other than to work around a compiler limitation or bug.

Using directives

Do not put a using directive in a header file other than for one of the following cases:

  • Inside a class for a base class function for which an override is being defined in the subclass
  • To support backwards compatibility when a symbol is moved from one namespace to another. In this situation, only apply the using directive to the renamed symbol. The effect of the using directive should be kept to the minimum required in order to achieve backwards compatibility for the renamed symbol.

Self-sufficiency

  • It should be possible to #include any header on its own (whether internal or public) and not generate a compiler error due to missing symbols, etc.
  • No header should depend on another header having been included before it.
  • Headers should also not generally depend on a particular symbol being defined before the header is included (it should #include its own set of headers as necessary to provide that symbol).

Comments

Copyright notices

  • Every source file should have a copyright notice at the very top. This includes:
    • Headers (.h),
    • Implementation files (.c, .cpp)
    • CMake project files (CMakeLists.txt)
  • Other text-based, source-like files that have support for comments should also generally contain a copyright notice.

The standard notice used for all Workspace code is the following:

============================================================================


  Copyright 2024 by:

    Commonwealth Scientific and Industrial Research Organisation (CSIRO)

    This file is licensed by CSIRO under the copy of the CSIRO Binary
    License Agreement included with the file when downloaded or obtained
    from CSIRO (including any Supplementary License).  If no copy was
    included, you must obtain a new copy of the Software from CSIRO before
    any use is permitted.

    For further information, contact: workspace@csiro.au

  This copyright notice must be included with all copies of the source code.

============================================================================
  • Plugin developers should come up with their own appropriate copyright notice rather than use the one above.
  • Plugin code should generally use a consistent copyright notice across all its source files.

Doxygen comments

  • Doxygen is the documentation system used throughout the Workspace project. Do not use MS Word, OpenOffice, LaTeX or other forms for documents.
  • As far as is reasonable, all documentation sources should be in doxygen-compatible form.
  • The Workspace code uses the backslash form for doxygen commands rather than the @ form to avoid potential issues with CMake misinterpreting the @ symbol.
  • Multi-line comments generally have each line starting with an asterisk (see examples further below).
  • Documentation blocks should be adjacent to the entity they are documenting, usually being placed immediately before the entity (variables and enums may have short trailing comment blocks).
  • The documentation block for a particular entity should generally be placed where that entity is defined, not declared.
  • When referring to a function by name, include brackets but not parameters (unless you need to distinguish a specific overload).

Classes

  • Classes have their comment block before the class definition, not where the class might be forward declared.
  • All class definitions should have a doxygen comment block and that documentation block should not be empty.
  • If the class is part of the public interface, it must have a one-line \brief statement at the top, followed by a blank line.
  • The brief description should not be excessively long. Save longer descriptions for the class details.
  • If the class is an internal implementation detail, the comment block should contain an \internal command on the first line, on its own.
  • Any additional documentation after an \internal directive is optional.
  • If the class is a template class, the template parameters should be defined with \tparam and appear after the \brief section, but before the main description of the class.
  • Following the class description, any \relates or \sa (ie "see also") blocks should be included. These should be the last sections in the documentation block.
/**
 * \brief Implements a specific magic trick
 *
 * This class does all sorts of interesting things.
 * Most of it is magic, some is just pure genius.
 *
 * \relates PerformMagic
 */
class MagicTrick
{
    // Contents of the public class
};


/**
 * \brief Example of a template class
 *
 * \tparam TrickT Specifies the type of trick the class defines
 */
template<typename TrickT>
class PerformTrick
{
    // Contents of the public class
};


/**
 * \internal
 */
class HiddenSecret
{
    // Move along, nothing to see here...
};

Functions

  • Functions are generally documented where the function body is defined, not in the header file where it will usually have been declared.
  • If a function has no implementation (eg a pure virtual function), it should be documented where it is declared, which is usually in a header.
  • All function implementations should have a doxygen comment block, even if it is left empty. Note that this also applies to member functions of internal implementation classes.
  • There should be two blank lines above a function documentation block to separate it from the preceding block (this makes it much easier to find functions in text editors that support syntax highlighting).
  • All functions that are part of the public interface should have non-empty comment blocks unless:
    • The function name already makes clear what the function does and
    • No further information needs to be provided to the developer
  • All function parameters should be documented with their own \param entry at the start of the comment block.
  • If the function is a templated function, the template parameters should be defined with \tparam and appear before the regular function parameters.
  • If you need to refer to a parameter by name somewhere in the documentation block, use the \a doxygen command with it to ensure it is formatted correctly.
  • Return values should be documented with the \return command at the end of the comment block.
  • The detailed description of what the function does, etc. usually sits between the parameters and the return value details.
  • Any pre-conditions for the function can be explicitly stated with the \pre command, usually placed before or after the description (whichever is more readable)
  • The same applies to post-conditions explicitly stated with the \post command.
  • If the function is a setter or getter, its companion function should be referenced with a \sa (ie "see also") command.
  • Any other functions that have a close relationship to the one being documented should also be listed in the \sa command.
  • If present, the \sa block should always be the last section in the documentation block.
    // ... end of previous function
}


/**
 * \param xFactor  Some value that does extra special things.
 * \param required Switch specifying whether we want to use the \a xFactor.
 *
 * This function does some magical computation. It uses \a xFactor
 * only when \a required is \c true. Otherwise, it uses some other value.
 *
 * \return The magic factor, or pi if you are hungry.
 *
 * \sa foolGeneralPublic()
 */
double  computeMagic(double xFactor, bool required)
{
    // ....
}

Enums

  • Any enum that is part of the public interface must be documented. A description of the enum (without using the \brief command) is sufficient.
  • Each item in an enum should also be documented.
  • Unlike other documentation blocks, the blocks for items in an enum are usually placed after the item, as per the following example:
/**
 * Specifies what sort of magic tricks can be requested.
 */
enum SupportedMagicTricks
{
    SleightOfHand,    //!< Standard magician's trick
    Misdirection,     /*!< This can be a bit more involved which
                           is why its description is longer */
    GrandIllusion     //!< Not going to spoil the surprise!
};

Variables

Note
Variables are not normally made accessible directly in public classes and global variables are to be avoided, so most variables will be private implementation details. As such, variables do not generally need to be documented unless the developer wants to make some non-obvious detail clear. A common example would be to document some assumption about how the variable is used.
  • Local variables within functions, etc. can be documented however the developer sees fit, as long as readability is maintained and it is clear to others what the variables mean and how they are used.
  • Ordinary C/C++ comments are appropriate for variables rather than the special form used by doxygen.

Formatting

Defining variables

  • Variables should be declared one per line.
  • Any asterisk’s (*) or ampersands (&) that are used to specify that a variable is a pointer or reference respectively should be associated with the variable’s type, not with its name.
Correct Incorrect
int varOne;
int varTwo;
int varOne, varTwo;
int* varOne;
int& varTwo = ...;
int *varOne;
int &varTwo = ...;

Virtual, Final and Override qualifiers

  • One and only one of the Virtual, Final and Override qualifiers should be present on a given method declaration in a class. Writing more than one of these three is redundant and a potential source of errors.
    • The Virtual keyword should only be used to declare a new virtual method on a class. It should not* be used on overrides in derived classes.
    • The Override qualifier should only be used to declare a non-final override of a base-class method.
    • The Final qualifier should only be used to declare a final override of a base-class method.
#include <iostream>

class BaseClass
{
public:
    virtual bool virtualMethod() const = 0;
};

class DerivedClassOne : public BaseClass
{
public:
    void virtualMethod() const override
    {
        std::cout << "This implementation is a non-final override." << std::endl;
    }

    virtual newVirtualMethod() const
    {
        std::cout << "This implementation is a new virtual method." << std::endl;
    }
};

class DerivedClassTwo : public BaseClass
{
public:
    void virtualMethod() const final
    {
        std::cout << "This implementation is a final override." << std::endl;
    }
};

Placement of curly braces

  • The Workspace codebase broadly follows the 'Allman' style of braces.
  • All scope blocks for classes, namespaces, enums, if, for, while, functions, etc. should uses the same formatting for curly braces.
  • The opening brace and closing brace should both be on their own separate line.
  • The only exceptions to the above rules are:
    • Initializer lists, for which the braces should start on the same line as the assignment operator or constructor opening bracket, and end on the same line as the last item in the list. C++11 style braces are preferred, where there are no spaces between the braces and the first/last items in the list, matching how parentheses are used. For example:
      // Example 1: good
      std::array<int, 6> integers({1, 2, 3, 4, 5, 6});
      
      // Example 2: good
      std::array<double, 3> doubles({
          0.0,
          1.0,
          2.0});
      
      // Example 3: good
      double cStyleDoubles[3] = {
          0.0,
          1.0,
          2.0};
      
      // Example 4: bad
      float floats[3] =
      {
          0.0f,
          1.0f,
          2.0f
      };
      
      // Example 5: bad
      const char* strings[3] = {
          "oh",
          "hi",
          "there"
      };
      
      // Example 6: bad
      std::array<float, 3> moreFloats(
      {
          "oh",
          "hi",
          "there"
      });
      
    • Lambdas, for which the braces can start on the same line as the closing bracket of the parameter list, and end on the line after the final line of code in the lambda.
      // Example 1: good
      auto result = someFunc([](auto& param) {
         return 0.0;
      });
      
      // Example 2: good
      auto result = someFunc([](auto& param) { return 0.0; });
      
      // Example 3: bad
      auto result = someFunc(
        [](auto& param)
        {
         return 0.0;
        });
      
      // Example 4: bad
      auto result = someFunc([](auto& param)
      {
       return 0.0;
      });
      
    • Inside class definitions, trivial inline function implementations may be included on the same line as the function name as long as the resultant line is not overly long.
    • Always use braces in flow control statements. Omitting braces can have unexpected consequences. For example:
      /**
       * Some bad code
       */
       
      #define PRINT_VALUES(value1, value2) \
          cout << value1; \
          cout << ", "; \
          cout << value2;
      
      void someFunction(bool choice)
      {
          if(choice)
              PRINT_VALUES(1, 2);
      }
      
      expands to:
      void someFunction(bool choice)
      {
          if(choice)
              cout << 1;
          cout << ", ";
          cout << 2;
      }
      
      which is not what the writer would have intended. Always using braces avoids this pitfall.
Correct Incorrect
if (someExpression == true)
{
    // Do something
}
if (someExpression == true) {
    // Do something
}
Within a class definition only...
int getFoobar() const { return foobar_; }

if (someExpression == true) { doSomething(); }

Constructors

  • The initialisation lists for a constructor should generally place each item on its own line unless the number of items is small and they all fit comfortably on one line.
  • For multi-line initialisation lists, place commas at the end of a line (ie after a variable), not at the start (ie not before a variable).
  • Place the colon on the same line as the function name, with a space between the closing parenthesis and the colon for readability.
  • In normal circumstances, do not list variables or base classes if they are simply invoking the default constructor.
Correct Incorrect
Foo::Foo(int a, int b) : a_(a), b_(b) {} Foo::Foo(int a, int b) : // Something really long... ;)
Foo::Foo(int a, int b) :
    a_(a),
    b_(b)
{
}
Foo::Foo(int a, int b)
    :
    a_(a),
    b_(b)
{
}

Foo::Foo(int a, int b) :
      a_(a)
    , b_(b)
{
}

Indentation

  • Tab characters should not be inserted into source files, insert spaces instead.
  • The standard indentation size is four spaces.

The indentation guidelines are best shown by example:

/**
 * \brief Main outer namespace
 */
namespace A
{

/**
 * \brief Inner namespace for fun toys
 *
 * To avoid excessive indenting, don't indent inner namespaces ....
 */
namespace B
{
    // .... unless it is an anonymous or private namespace, in which
    // case you can indent it or not depending on your own preference
    namespace
    {
        // ...
    }

    /**
     * \brief Does nothing interesting
     */
    class Foo : public JustOneBase
    {
        // ...
    };


    /**
     * \brief Does some more interesting things
     */
    class Bar :
        public BaseOne,    // Indent multi-line base classes
        public BaseTwo
    {
        double  var1_;

    // Access specifiers have same indentation as the class keyword
    public:
        Bar();

        int getCrazyValue(int b);

        /**
         * \brief Inner class for interesting stuff
         */
        class Nested
        {
            // ... Same rules as for regular non-nested classes
        }

        /**
         * Toys for the adrenalin junky
         */
        enum CoolThings
        {
            Snowboards,   //!< For cold weather only
            DrumKits      //!< Use when neighbours are away
        };
    };


    /**
     * \param b Mystery switch
     *
     * \return A crazy value of little use.
     */
    int Bar::getCrazyValue(int b)
    {
        int a = 1;
        for (int i = 0; i != 20; ++i)
        {              // Don't indent curly braces....
            a += i;    // ... only indent the stuff within the braces
        }

        switch (b)
        {
        case 7:        // Don't indent the case keywords ...
            a += 42;   // ... but do indent the case body
            break;
        case 2:
            a -= 27;
            break;
        default:
            a += b;
        }

        return a;
    }
}}

Implementation

The following are a set of implementation guidelines not already covered by preceding sections. They are designed to provide some consistency in coding style and to avoid common programming pitfalls or suboptimal code. Many of these are discussed in more detail in the excellent book "C++ Coding Standards", by Herb Sutter.

Interfaces

  • Aim for "one entity, one responsibility". Eg, don't write classes that have multiple responsibilities.
  • Classes in the public API should hide their implementation using the pointer to implementation idiom. The name of the pointer is always pImpl_ in all Workspace code.
  • Exceptions to the pointer-to-implementation rule can be made for the following cases:
    • Performance-critical classes
    • Fully inlined or template classes
    • Classes that will only ever be used by one thing, such as a main application class with no prospect for re-use
    • Tutorial or example code where simplicity is of greater importance
  • Where public classes do have variables, make them private. Subclasses and external code should only access these variables via member functions.
  • Use const proactively. If a member function does not modify an object's data, make it const.
  • Do not return non-const references from a const function.
  • Avoid providing type conversion operators for a class unless it is genuinely intuitive for the developer to expect them (eg math-related classes).
  • Do not use anything from the STL in a public interface.
  • Do not include any STL header in one of your own public headers.
Note
The reason for excluding STL in the public API or headers is to allow clients to switch to a different STL implementation (eg STLPort). It may also be necessary on some platforms to ensure binary compatibility.

Signals and slots

The following are in addition to the rules for ordinary member functions:

  • Any types used as parameters must be fully scoped in the class definition, otherwise code in other namespaces cannot reliably create signal-slot connections.
  • Signals must always have return type void.
  • Slots should also generally return void since they should be designed primarily for code that will ignore any return value.

Exceptions

  • Use of exceptions is permitted, but prefer other mechanisms.
  • Do not use exception specifications on functions.
  • All exceptions must be caught within the shared library that generates them. Exceptions cannot be reliably caught by type across shared library boundaries.

See Scott Meyers' book "More Effective C++" for further good advice on exceptions.

Logging

  • Prefer logLine() instead of logText(), as logLine() automatically appends a carriage return at the end of the message you supply whereas logText() does not.

Auto

  • Use of auto is preferred for all variable definitions except for manually declared integral types and variables declared on the stack.
  • For pointers, use of either auto* and auto are acceptable. It is up to the developer to determine which most clearly illustrates the intent of their code.
  • Use auto when declaring iterators.

Loops

  • Prefer range-based loops to for / while loops where possible.
  • Where a for loop is required, use auto to infer the counter type. The following approach is recommended: auto numElems = container.size();
    for (auto i = decltype(numElems); i < numElems; ++i)

C++ language

  • Do not use goto.
  • Avoid initialisation dependencies across compilation units. Initialisation of an object in one file must not depend on an object from a different file being initialised, since the order of initialisation is not defined by the C++ standard.
  • Do not call virtual functions in constructors or destructors (they act as non-virtual in these contexts).
  • Avoid using const_cast except to work around const-correctness bugs in third party code.
  • Avoid using reinterpret_cast except where required to be compatible with low level code (very rare).
  • Do not use C-style casts, use the C++ style static_cast or dynamic_cast as appropriate instead.
  • Avoid type switching with if or switch statements. Prefer proper class design with virtual functions or templated solutions.
  • Prefer to use iterators ahead of raw integer indexes into containers.
  • Declare variables as locally as possible (ie declare them at the point where you need them).
  • Avoid long function bodies. Break them up into smaller chunks to improve readability.
  • Prefer prefix ++ and -- operators over postfix. Eg, ++a is preferred over a++ where either could be used.
  • Do not use assert(). Instead, use one of the Workspace assert macros defined in "Workspace/Application/LanguageUtils/errorchecks.h". They should be used liberally to verify any assumptions or as a defensive measure to catch problems early. Debug-only macros should be accompanied by equivalent protective code for Release.
  • Compile cleanly at high warning levels and don't ignore compiler warnings.

References

The following are some references that have had a strong influence on the Workspace source code, both in terms of style and implementation. They are highly recommended for anyone interested in C++ development in general.