best_practices
no way to compare when less than two revisions
Differences
This shows you the differences between two versions of the page.
Previous revisionNext revision | |||
— | best_practices [2016/03/14 12:31] – jmgr | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== Best Practices ====== | ||
+ | ===== General ====== | ||
+ | |||
+ | ==== Tips & tricks ==== | ||
+ | |||
+ | === Always initialise primitive variables (other types do not require this) === | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | int myVariable; | ||
+ | std::string myString; | ||
+ | |||
+ | // Good | ||
+ | int myVariable{}; | ||
+ | std::string myString; // std::string is not a primitive variable, so initialisation is optional | ||
+ | </ | ||
+ | |||
+ | === Always set parameters as const reference if they are non-primitives === | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | void myFunction(int firstParameter, | ||
+ | { | ||
+ | //... | ||
+ | } | ||
+ | |||
+ | // Good | ||
+ | void myFunction(int firstParameter, | ||
+ | { | ||
+ | //... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === Do not use "using namespace" | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | using namespace std; | ||
+ | |||
+ | void myFunction() | ||
+ | { | ||
+ | string myString; | ||
+ | //... | ||
+ | } | ||
+ | |||
+ | // Good | ||
+ | void myFunction() | ||
+ | { | ||
+ | using namespace std; | ||
+ | | ||
+ | string myString; | ||
+ | //... | ||
+ | } | ||
+ | |||
+ | // Better | ||
+ | void myFunction() | ||
+ | { | ||
+ | std::string myString; | ||
+ | //... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Note that you can use namespace aliases to simplify a complex namespace hierarchy: | ||
+ | |||
+ | <code cpp> | ||
+ | namespace myNamespace = some:: | ||
+ | </ | ||
+ | |||
+ | === Use references instead of pointers if possible => easier to use === | ||
+ | (but note that Qt uses a lot of pointers for historical reasons) | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | void myFunction(MyObject *object) | ||
+ | { | ||
+ | object-> | ||
+ | //... | ||
+ | } | ||
+ | |||
+ | // Good | ||
+ | void myFunction(MyObject & | ||
+ | { | ||
+ | object.function(); | ||
+ | //... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === Use smart pointers instead of raw/ | ||
+ | (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//, see [[best_practices# | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | MyObject *object = new MyObject; | ||
+ | |||
+ | delete object; | ||
+ | |||
+ | // Good | ||
+ | #include < | ||
+ | |||
+ | std:: | ||
+ | |||
+ | // Good | ||
+ | #include < | ||
+ | |||
+ | // parentObject is a pointer to a QObject | ||
+ | |||
+ | MyObject *object = new MyObject(parentObject); | ||
+ | </ | ||
+ | |||
+ | Note the use of std:: | ||
+ | |||
+ | === Never mix C and C++ code === | ||
+ | C and C++ are very different languages. The fact that most of the C language can be compiled using a C++ compiler does not mean that you should mix both. For example, C-arrays are difficult to use correctly and should be avoided. Prefer using QList when writing Qt-based code and std::vector otherwise. | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | int *myArray = new int[42]; | ||
+ | |||
+ | delete[] myArray ; | ||
+ | |||
+ | // Good | ||
+ | #include < | ||
+ | |||
+ | QList< | ||
+ | |||
+ | // Good | ||
+ | #include < | ||
+ | |||
+ | std:: | ||
+ | </ | ||
+ | |||
+ | Static arrays should use std::array. They offer the same performance while adding convenience functions like size(). | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | int myArray[42]; | ||
+ | |||
+ | // Good | ||
+ | #include < | ||
+ | |||
+ | std:: | ||
+ | </ | ||
+ | |||
+ | In addition, note that C code relying on functions to free memory like free(), are not exception safe. This means that mixing exception-throwing code with these functions would trigger memory leaks in case an exception is thrown. | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | int* stuff = (int*)malloc(sizeof(int) * 42); | ||
+ | |||
+ | QPushButton *button = new QPushButton(parent); | ||
+ | |||
+ | free(stuff); | ||
+ | |||
+ | // Good | ||
+ | #include < | ||
+ | |||
+ | std:: | ||
+ | |||
+ | QPushButton *button = new QPushButton(parent); | ||
+ | </ | ||
+ | |||
+ | === If you have to write a function that returns multiple values, prefer returning a std::tuple instead of using reference parameters (if possible) === | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | void myFunction(int & | ||
+ | { | ||
+ | outFirstVariable = 42; | ||
+ | outSecondVariable = " | ||
+ | } | ||
+ | |||
+ | // Good | ||
+ | #include < | ||
+ | |||
+ | std:: | ||
+ | { | ||
+ | return std:: | ||
+ | } | ||
+ | |||
+ | // auto result = myFunction(); | ||
+ | // Use std:: | ||
+ | </ | ||
+ | |||
+ | === When using events, prefer using lambdas (anonymous functions) instead of static functions === | ||
+ | |||
+ | <code cpp> | ||
+ | #include < | ||
+ | |||
+ | QPushButton *button = new QPushButton(parent); | ||
+ | |||
+ | // Bad | ||
+ | void MyObject:: | ||
+ | { | ||
+ | //... | ||
+ | } | ||
+ | |||
+ | connect(button, | ||
+ | |||
+ | // Good | ||
+ | connect(button, | ||
+ | { | ||
+ | //... | ||
+ | }); | ||
+ | </ | ||
+ | |||
+ | === When incrementing a variable, prefer using the prefix operator rather than the postfix one === | ||
+ | It is sometimes faster, but never slower: http:// | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | i++; | ||
+ | |||
+ | // Good | ||
+ | ++i; | ||
+ | </ | ||
+ | |||
+ | === When performing operations on containers (arrays, vectors, etc.) prefer using range-based for (= " | ||
+ | |||
+ | <code cpp> | ||
+ | std:: | ||
+ | |||
+ | // Bad | ||
+ | for(std:: | ||
+ | { | ||
+ | //... | ||
+ | } | ||
+ | |||
+ | // Not as bad | ||
+ | for(int index = 0; index < myContainer.size(); | ||
+ | { | ||
+ | //... | ||
+ | } | ||
+ | |||
+ | // Good | ||
+ | for(int value: myContainer) | ||
+ | { | ||
+ | //... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === Never forward-declare variables === | ||
+ | |||
+ | <code 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) | ||
+ | { | ||
+ | //... | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === Never use typedef === | ||
+ | It has been superseded by //using// since C++11. | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | typedef int MyInteger; | ||
+ | |||
+ | // Good | ||
+ | using MyInteger = int; | ||
+ | </ | ||
+ | |||
+ | Note that using //using// you can also set template parameters now: | ||
+ | |||
+ | <code cpp> | ||
+ | using Integer3DVector = Generic3DVector< | ||
+ | </ | ||
+ | |||
+ | === Never use #define to create constants, use constexpr instead === | ||
+ | <code cpp> | ||
+ | // Bad | ||
+ | #define MY_CONSTANT_VALUE 42 | ||
+ | |||
+ | // Good | ||
+ | constexpr int MyConstantValue = 42; // Note the naming change here, caps should only be used for preprocessor defines | ||
+ | </ | ||
+ | |||
+ | === Prefer using enum classes rather that enums === | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | enum Color | ||
+ | { | ||
+ | BlueColor, | ||
+ | RedColor | ||
+ | }; | ||
+ | |||
+ | Color myColor = BlueColor; | ||
+ | |||
+ | // Good | ||
+ | enum class Color | ||
+ | { | ||
+ | Blue, | ||
+ | Red | ||
+ | }; | ||
+ | |||
+ | Color myColor = Color:: | ||
+ | </ | ||
+ | |||
+ | Please note that contrary to enums, enum classes cannot be implicitly converted to an integer. Use a static_cast if you need to convert from an integer to an enum class and vice-versa. | ||
+ | |||
+ | === Never use C-style casts, use C++ ones === | ||
+ | <code cpp> | ||
+ | enum class Color | ||
+ | { | ||
+ | Blue, | ||
+ | Red | ||
+ | }; | ||
+ | |||
+ | Color color{Color:: | ||
+ | int colorAsInteger{}; | ||
+ | |||
+ | // Bad | ||
+ | colorAsInteger = (int)color; | ||
+ | Color otherColor = (Color)colorAsInteger; | ||
+ | |||
+ | // Good | ||
+ | colorAsInteger = static_cast< | ||
+ | Color otherColor = static_cast< | ||
+ | </ | ||
+ | |||
+ | When dealing with QObject-based classes you should use [[http:// | ||
+ | |||
+ | === Header files should not depend on other #includes === | ||
+ | To test this, just include your header in an empty source file, if it doesn' | ||
+ | |||
+ | === Header files should be grouped by library and sorted, system headers should be at the bottom === | ||
+ | This helps readability. Putting system headers at the bottom helps ensuring that the previous rule is enforced. | ||
+ | |||
+ | <code cpp> | ||
+ | // Local headers | ||
+ | #include " | ||
+ | |||
+ | // Library headers | ||
+ | #include < | ||
+ | |||
+ | // System headers | ||
+ | #include < | ||
+ | #include < | ||
+ | </ | ||
+ | |||
+ | === Prevent dependency contamination === | ||
+ | Use case: you are using an " | ||
+ | |||
+ | <file cpp myclass.hpp> | ||
+ | #pragma once | ||
+ | |||
+ | #include < | ||
+ | |||
+ | class MyClass final | ||
+ | { | ||
+ | public: | ||
+ | MyClass() = default; | ||
+ | | ||
+ | private: | ||
+ | HWND m_window; | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | In this case, every file #including " | ||
+ | This dependency cannot be removed because you need to #include Windows.h to be able to use the type HWND. | ||
+ | |||
+ | Solution: here we can use the " | ||
+ | |||
+ | <file cpp myclass_private.hpp> | ||
+ | #pragma once | ||
+ | |||
+ | #include < | ||
+ | |||
+ | struct MyClass_Private | ||
+ | { | ||
+ | HWND window; | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | <file cpp myclass.hpp> | ||
+ | #pragma once | ||
+ | |||
+ | #include < | ||
+ | |||
+ | struct MyClass_Private; | ||
+ | |||
+ | class MyClass final | ||
+ | { | ||
+ | public: | ||
+ | MyClass(); | ||
+ | | ||
+ | private: | ||
+ | std:: | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | <file cpp myclass.cpp> | ||
+ | #include " | ||
+ | #include " | ||
+ | |||
+ | MyClass:: | ||
+ | m_private{std:: | ||
+ | { | ||
+ | // Access window by using m_private-> | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | And that's it. It is indeed an increase in complexity and has a slight impact on performance, | ||
+ | |||
+ | === Write small functions instead of huge ones === | ||
+ | === Never call virtual functions from a constructor === | ||
+ | Reference: [[https:// | ||
+ | === Classes and variables === | ||
+ | You should not consider a class as a collection of variables, but rather like a service provider. That is the reason why you should put the public functions first, before the private ones. Variables should be put at the end, since they are an implementation detail. | ||
+ | |||
+ | ===== Comments ===== | ||
+ | What amount of comments should you write? Too many comments will make the code unreadable (see AutoHotKey for an example). | ||
+ | |||
+ | TODO: Add something about Doxygen and how to write compatible comments | ||
+ | |||
+ | <code cpp> | ||
+ | // Bad | ||
+ | void stuff(int blih) | ||
+ | { | ||
+ | int blah = 5; // We set blah to 5 here | ||
+ | int bloh = std:: | ||
+ | | ||
+ | // | ||
+ | | ||
+ | return blah; // We return the value of the variable blah of type integer. We then terminate this function and continue our merry way into our program. Yep. That's it. Bye. | ||
+ | } | ||
+ | |||
+ | // Good | ||
+ | // Note that this " | ||
+ | void stuff(int blih) | ||
+ | { | ||
+ | int blah = 5; | ||
+ | // We need to compute bloh here because... | ||
+ | int bloh = std:: | ||
+ | | ||
+ | // | ||
+ | | ||
+ | return blah; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Qt specific ===== | ||
+ | |||
+ | ==== Containers ==== | ||
+ | |||
+ | ^ C++ Standard Library ^ Qt ^ | ||
+ | | [[http:// | ||
+ | | [[http:// | ||
+ | | [[http:// | ||
+ | | [[http:// | ||
+ | | [[http:// | ||
+ | | [[http:// | ||
+ | | [[http:// | ||
+ | |||
+ | *If in doubt, use [[https:// | ||
+ | |||
+ | **/!\ [[http:// | ||
+ | |||
+ | Prefer using [[https:// | ||
+ | ==== 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 | ||
+ | |||
+ | <file cpp myobject.hpp> | ||
+ | #pragma once | ||
+ | |||
+ | #include < | ||
+ | |||
+ | class MyObject: public QObject | ||
+ | { | ||
+ | Q_OBJECT | ||
+ | | ||
+ | public: | ||
+ | MyObject(QObject *parent = nullptr); | ||
+ | virtual ~MyObject(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <file cpp myobject.cpp> | ||
+ | #include " | ||
+ | |||
+ | MyObject:: | ||
+ | QObject(parent) | ||
+ | { | ||
+ | } | ||
+ | |||
+ | 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 | ||
+ | |||
+ | <file cpp mywidget.hpp> | ||
+ | #pragma once | ||
+ | |||
+ | #include < | ||
+ | |||
+ | class MyWidget: public QWidget | ||
+ | { | ||
+ | Q_OBJECT | ||
+ | | ||
+ | public: | ||
+ | MyClass(QWidget *parent = nullptr); | ||
+ | virtual ~MyClass(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <file cpp mywidget.cpp> | ||
+ | #include " | ||
+ | |||
+ | MyWidget:: | ||
+ | QWidget(parent) | ||
+ | { | ||
+ | } | ||
+ | |||
+ | MyWidget:: | ||
+ | { | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Other classes: | ||
+ | * memory management through smart pointers | ||
+ | |||
+ | ==== Exceptions ==== | ||
+ | |||
+ | [[https:// | ||
+ | |||
+ | If you are writing non-Qt code then you really should use exceptions and more importantly, | ||
+ | |||
+ | ====== Examples ====== | ||
+ | |||
+ | <code cpp> | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | // 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 & | ||
+ | m_myVar{52}, | ||
+ | m_myString{myString} | ||
+ | { | ||
+ | } | ||
+ | |||
+ | // Entity semantic: always forbid copy & assignment | ||
+ | MyExampleClass(const MyExampleClass &) = delete; | ||
+ | MyExampleClass & | ||
+ | |||
+ | private: | ||
+ | // Variables are always at the end, because they represent an implementation detail | ||
+ | int m_myVar{42}; | ||
+ | std::string m_myString{" | ||
+ | }; | ||
+ | |||
+ | // 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 & | ||
+ | m_x(other.m_x), | ||
+ | m_y(other.m_y) | ||
+ | { | ||
+ | } | ||
+ | |||
+ | MyVector & | ||
+ | { | ||
+ | std:: | ||
+ | std:: | ||
+ | |||
+ | 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 " | ||
+ | { | ||
+ | // Allocation on the stack (fast) | ||
+ | MyExampleClass myExampleClass{" | ||
+ | // myExampleClass is destroyed here | ||
+ | } | ||
+ | { | ||
+ | // Allocation on the heap (slower, allows polymorphism) | ||
+ | std:: | ||
+ | // Or (with auto) | ||
+ | auto myExampleClass{std:: | ||
+ | // myExampleClass is destroyed here (thanks to the smart pointer) | ||
+ | } | ||
+ | } | ||
+ | </ |
best_practices.txt · Last modified: 2023/04/25 16:52 by 127.0.0.1