User Tools

Site Tools


coding_style

This is an old revision of the document!


Best Practices

General

Tips & tricks

Always initialise primitive variables (other types do not require this)

<sxh cpp> Bad int myVariable; std::string myString; Good int myVariable{}; std::string myString; </sxh>

Always set parameters as const reference if they are non-primitives

<sxh cpp> Bad void myFunction(int firstParameter, std::string secondParameter) { … }

Good void myFunction(int firstParameter, const std::string &secondParameter) { … } </sxh>

Do not use "using namespace" outside of a function => improves readability (and only use it when needed)

<sxh cpp> Bad using namespace std; void myFunction() { string myString; … }

Good void myFunction() { using namespace std; string myString; … } </sxh>

Note that you can use namespace aliases to simplify a complex namespace hierarchy:

<sxh cpp> namespace myNamespace = some::complex::namespace::hierarchy; </sxh>

Use references instead of pointers if possible => easier to use (but note that Qt uses a lot of pointers for historical reasons)

<sxh cpp> Bad void myFunction(MyObject *object) { object→function(); … }

Good void myFunction(MyObject &object) { object.function(); … } </sxh>

Use smart pointers instead of raw/naked/dumb pointers => easier to use (but note that Qt uses its own memory management system, so if using Qt classes or Qt-based classes you will have to use //new// at least)

<sxh cpp> Bad MyObject *object = new MyObject; delete object; Good #include <memory>

std::unique_ptr<MyObject> object{std::make_unique<MyObject>()};

Good #include <QObject> parentObject is a pointer to a QObject

MyObject *object = new MyObject(parentObject); </sxh>

If you have to write a function that returns multiple values, prefer returning a std::tuple instead of using reference parameters (if possible)

<sxh cpp> Bad void myFunction(int &outFirstVariable, std::string &outSecondVariable) { outFirstVariable = 42; outSecondVariable = “text”; } Good #include <tuple>

std::tuple<int, std::string> myFunction() {

return std::make_tuple(42, "text");

}

auto result = myFunction(); Use std::get<0>(result) to get the integer, std::get<1>(result) to get the std::string </sxh>

When using events, prefer using lambdas (nameless functions) instead of static functions

<sxh cpp> #include <QPushButton>

QPushButton *button = new QPushButton(parent);

Bad void MyObject::onClick() { … }

connect(button, SIGNAL(clicked()), this, SLOT(onClick()));

Good connect(button, &QPushButton::clicked(), [this]() { … }); </sxh>

Never forward-declare variables

<sxh cpp> Bad int i; int j; for(; i < 10; ++i) { for(; j < 10; ++j) {

}

}

Good for(int i{}; i < 10; ++i) { for(int j{}; j < 10; ++j) {

}

} </sxh>

Write small functions instead of huge ones

Never call virtual functions from a constructor: https://stackoverflow.com/questions/496440/c-virtual-function-from-constructor

Qt specific

Containers

*If in doubt, use QList.

/!\ std::list is not equivalent to QList /!\

Prefer using QHash and QMultiHash over QMap and QMultiMap if you don't need the items to be sorted, their lookup time is smaller.

Inheritance

QObject-based:

  • automatic memory management (no smart pointer required)
  • constructor takes a parent QObject, defaulted to nullptr
  • Q_OBJECT macro at the beginning of the class
myobject.hpp
#pragma once
 
#include <QObject>
 
class MyObject: public QObject
{
  Q_OBJECT
 
public:
  MyObject(QObject *parent = nullptr);
  virtual ~MyObject();
}
myobject.cpp
#include "myobject.hpp"
 
MyObject::MyObject(QObject *parent):
  QObject(parent)
{
}
 
MyObject::~MyObject()
{
}

QWidget-based:

  • automatic memory management (no smart pointer required)
  • constructor takes a parent QWidget, defaulted to nullptr
  • Q_OBJECT macro at the beginning of the class
mywidget.hpp
#pragma once
 
#include <QWidget>
 
class MyWidget: public QWidget
{
  Q_OBJECT
 
public:
  MyClass(QWidget *parent = nullptr);
  virtual ~MyClass();
}
mywidget.cpp
#include "mywidget.hpp"
 
MyWidget::MyWidget(QWidget *parent):
  QWidget(parent)
{
}
 
MyWidget::~MyWidget()
{
}

Other classes:

  • memory management through smart pointers

Exceptions

Exceptions allow the developer to use various features without having to constantly check for errors. Even if you are not using them explicitly they may be triggered by the standard library or even by new. Sadly, for historical reasons, Qt does not support them. This means that if you are using a feature coming from a third party library you have to catch exceptions to prevent issues with Qt code. Note that Qt containers are exception proof however.

If you are writing non-Qt code then you really should use exceptions and more importantly, write exception-safe code. Using smart pointers is a great and easy way to do this. For Qt-based code you will have to use C-style error checking based on booleans and “getErrorString” functions. This is, for me, Qt's main drawback.

Style

Code files

Code separation

Generally speaking, each class/struct should have its declaration in one file (header) and its definition in another file (source file). If a class/struct is very small then it can be contained in a header, for example if it only contains variables and no functions. Qt specific: note that QObject/QWidget-based classes have to have their own header file in order to the MOC (Qt's meta compiler) to find them.

Naming

Source files should have the .cpp extension (common practice) and header files the .hpp extension. Note here that the .h extension may be more common, but I personally think that the .hpp extension makes more sense since C++ headers are not generally compatible with the C language.

Header guards

Header guards are a C++-specific feature kept for historical reasons. The official way of using them is to put all the header code between #defines:

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H
 
//...
 
#endif // MY_HEADER_FILE_H

However, in practice I find this to be dangerous and I have encountered some particularly serious bugs using this method. Indeed, if you happen to copy/paste a header file you will need to remember having to change the header guard #define name. If you do not do this and if you include both header files, only the first one will be effectively included.

Another way to shoot yourself in the foot using header guards is the use of a namespace. Since preprocessor instructions cannot use them you will get into trouble if a header file within a namespace has the same name as another header file outside of it.

For these reasons, I recommend instead the use of the non-standard feature called “pragma once”:

#pragma once
 
//...

This feature in implemented in all known compilers and is both easier to use and foolproof. It has not been accepted into the C++ standard because of some particular cases, like having source files on multiple “remote mounts”: https://stackoverflow.com/questions/23696115/is-pragma-once-part-of-the-c11-standard.

Examples

#include <string>
#include <memory>
 
// An entity semantic class
class MyExampleClass final
{
public:
    // This constructor is explicit to prevent something like this: MyExampleClass test = "some text";
    explicit MyExampleClass(const std::string &myString):
        m_myVar{52},
        m_myString{myString}
    {
    }
 
    // Entity semantic: always forbid copy & assignment
    MyExampleClass(const MyExampleClass &) = delete;
    MyExampleClass &operator=(const MyExampleClass &) = delete;
 
private:
    // Variables are always at the end, because they represent an implementation detail
    int m_myVar{42};
    std::string m_myString{"value"};
};
 
// A value semantic class
class MyVector final // Always final: a value semantic class should *never* be inherited from
{
public:
    // We want to use the default implementation (wich is faster than anything we can do)
    MyVector() = default;
 
    MyVector(const MyVector &other):
        m_x(other.m_x),
        m_y(other.m_y)
    {
    }
 
    MyVector &operator=(MyVector other)
    {
        std::swap(m_x, other.m_x);
        std::swap(m_y, other.m_y);
 
        return *this;
    }
 
private:
    // m_x and m_y are initialized with the default value for the type int: 0
    // unless the initializer list in a constructor decides otherwise
    int m_x{};
    int m_y{};
};
 
void test()
{
    // I use scopes "{}" here to limit where my variables live and are accessible
    {
        // Allocation on the stack (fast)
        MyExampleClass myExampleClass{"some text"};
        // myExampleClass is destroyed here
    }
    {
        // Allocation on the heap (slower, allows polymorphism)
        std::unique_ptr<MyExampleClass> myExampleClass{std::make_unique<MyExampleClass>("some text")};
        // Or (with auto)
        auto myExampleClass{std::make_unique<MyExampleClass>("some text")};
        // myExampleClass is destroyed here (thanks to the smart pointer)
    }
}
coding_style.1456921699.txt.gz · Last modified: 2023/04/25 16:52 (external edit)