User Tools

Site Tools


best_practices

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
best_practices [2016/03/10 12:49] jmgrbest_practices [2023/04/25 16:52] (current) – external edit 127.0.0.1
Line 5: Line 5:
 ==== Tips & tricks ==== ==== Tips & tricks ====
  
-=== Always initialise primitive variables (other types do not require this) ===+=== Always initialize primitive variables (other types do not require this) ===
  
 <code cpp> <code cpp>
Line 14: Line 14:
 // Good // Good
 int myVariable{}; int myVariable{};
-std::string myString;+std::string myString; // std::string is not a primitive variable, so initialization is optional
 </code> </code>
  
Line 108: Line 108:
 MyObject *object = new MyObject(parentObject); MyObject *object = new MyObject(parentObject);
 </code> </code>
 +
 +Note the use of std::make_unique here; it should be used instead of //new// when using std::unique_ptr.
  
 === Never mix C and C++ code === === 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 languages. 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. +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> <code cpp>
Line 139: Line 141:
  
 std::array<int, 42> myArray; std::array<int, 42> myArray;
 +</code>
 +
 +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); // What happens if new triggers an exception? "stuff" is never freed and a memory leak occurs
 +
 +free(stuff);
 +
 +// Good
 +#include <array>
 +
 +std::array<int, 42> stuff;
 +
 +QPushButton *button = new QPushButton(parent); // "stuff" never leaks memory
 </code> </code>
  
Line 163: Line 183:
 </code> </code>
  
-=== When using events, prefer using lambdas (nameless functions) instead of static functions ===+=== When using events, prefer using lambdas (anonymous functions) instead of static functions ===
  
 <code cpp> <code cpp>
Line 183: Line 203:
     //...     //...
 }); });
 +</code>
 +
 +=== When incrementing a variable, prefer using the prefix operator rather than the postfix one ===
 +It is sometimes faster, but never slower: http://stackoverflow.com/questions/24901/is-there-a-performance-difference-between-i-and-i-in-c
 +
 +<code cpp>
 +// Bad
 +i++;
 +
 +// Good
 +++i;
 +</code>
 +
 +=== When performing operations on containers (arrays, vectors, etc.) prefer using range-based for (= "foreach") instead of index or iterators when possible ===
 +
 +<code cpp>
 +std::vector<int> myContainer = {42, 43, 44, 45};
 +
 +// Bad
 +for(std::vector<int>::iterator it = myContainer.begin(); it != myContainer.end(); ++it)
 +{
 +    //...
 +}
 +
 +// Not as bad
 +for(int index = 0; index < myContainer.size(); ++index )
 +{
 +    //...
 +}
 +
 +// Good
 +for(int value: myContainer)
 +{
 +    //...
 +}
 </code> </code>
  
Line 235: Line 290:
 constexpr int MyConstantValue = 42; // Note the naming change here, caps should only be used for preprocessor defines constexpr int MyConstantValue = 42; // Note the naming change here, caps should only be used for preprocessor defines
 </code> </code>
 +
 +=== 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::Blue;
 +</code>
 +
 +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::Red};
 +int colorAsInteger{};
 +
 +// Bad
 +colorAsInteger = (int)color;
 +Color otherColor = (Color)colorAsInteger;
 +
 +// Good
 +colorAsInteger = static_cast<int>(color);
 +Color otherColor = static_cast<Color>(colorAsInteger);
 +</code>
 +
 +When dealing with QObject-based classes you should use [[http://doc.qt.io/qt-5/qobject.html#qobject_cast|qobject_cast]].
 +
 +=== Header files should not depend on other #includes ===
 +To test this, just include your header in an empty source file, if it doesn't compile then you should add the missing #include directives.
 +
 +=== 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 "MyClass.hpp"
 +
 +// Library headers
 +#include <QDebug>
 +
 +// System headers
 +#include <cmath>
 +#include <cstdio>
 +</code>
 +
 +=== Prevent dependency contamination ===
 +Use case: you are using an "enum" or "#defines" from a system header, lets say Windows.h.
 +
 +<file cpp myclass.hpp>
 +#pragma once
 +
 +#include <Windows.h>
 +
 +class MyClass final
 +{
 +public:
 +    MyClass() = default;
 +    
 +private:
 +    HWND m_window;
 +};
 +</file>
 +
 +In this case, every file #including "myclass.hpp" will also implicitly #include Windows.h. Ouch.
 +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 "private implementation" idiom, or "pimpl". I consists in having all the member variables in a separate struct.
 +
 +<file cpp myclass_private.hpp>
 +#pragma once
 +
 +#include <Windows.h>
 +
 +struct MyClass_Private
 +{
 +    HWND window;
 +};
 +</file>
 +
 +<file cpp myclass.hpp>
 +#pragma once
 +
 +#include <memory>
 +
 +struct MyClass_Private;
 +
 +class MyClass final
 +{
 +public:
 +    MyClass();
 +    
 +private:
 +    std::unique_ptr<MyClass_Private> m_private;
 +};
 +</file>
 +
 +<file cpp myclass.cpp>
 +#include "myclass_private.hpp"
 +#include "myclass.hpp"
 +
 +MyClass::MyClass():
 +    m_private{std::make_unique<MyClass_Private>()}
 +{
 +    // Access window by using m_private->window
 +}
 +</file>
 +
 +And that's it. It is indeed an increase in complexity and has a slight impact on performance, but reduces compilation time and prevents clashes between symbols and #defines (and Windows.h creates a huge pile of mess when included).
  
 === Write small functions instead of huge ones === === Write small functions instead of huge ones ===
 === Never call virtual functions from a constructor === === Never call virtual functions from a constructor ===
 Reference: [[https://stackoverflow.com/questions/496440/c-virtual-function-from-constructor]] Reference: [[https://stackoverflow.com/questions/496440/c-virtual-function-from-constructor]]
 +=== 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::max(blih, 8); // Here we compute the maximum value between blih and 8, and then we store the result in a variable called bloh
 +    
 +    //...  
 +    
 +    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 "example" has other issues: the function and the variables have meaningless names, and there is a magic value
 +void stuff(int blih)
 +{
 +    int blah = 5;
 +    // We need to compute bloh here because...
 +    int bloh = std::max(blih, 8);
 +    
 +    //...  
 +    
 +    return blah;
 +}
 +</code>
 +
 ===== Qt specific ===== ===== Qt specific =====
  
best_practices.1457614176.txt.gz · Last modified: 2023/04/25 16:52 (external edit)