Smart pointers
Avoid:
1 | class MyService{}; |
Better:
1 | class MyService{}; |
Reasoning:
- RAII is used to control the lifetime of allocated objects.
- Pointers and references are needed to support polymorphism, handle with care!
- Avoid using
new
anddelete
manually. - When you need a single memory ownership use
std::unique_ptr
. - When you need a multiple memory ownership use
std::shared_ptr
.
Generics and TypeDefs (Using)
Avoid:
1 | void Send(Message message, InnerType type) |
Better:
1 | template <class TMsg, class TType = typename TMsg::inner_type> |
Reasoning:
Generics help us to write reuseable code.
We accomplish this by passing the parameters as generic types.
In this example we make use of type definitions using inner_type = std::string
.
Instead of providing an enum
we use the inner_type
definition from within the type.
Rule of 0, 3, 5
Avoid:
1 | class A |
Better:
1 | class A |
Reasoning:
Keep track of your explicit resources.
Using these rules you can avoid memory leaks and bad assignments.
Rule of 0 - If a class doesn’t define custom ctors, copy or dtors, it’s not necessary to add them to the class.
Rule of 3 - If a class defines dtor, copy ctor or copy assignment, it most needs all 3 of them.
Rule of 5 - If a class requires a move ctor, move assignment it most need all 5 of them.
Feature checks
Avoid:
1 | void MyFunc(const char* someBuffer) |
Better:
1 | void MyFunc(const char* someBuffer) |
Reasoning:
Generic libraries may need backward compatability for older compiler toolsets.
Any code that needs to be agnostic to it may use the Feature checks.
This can assist to transfer new features to older code bases.
Another way to handle it is to specify a minimum C++ version for your library.
Or add which C++ versions it supports.
Concepts - C++ 20
Avoid:
1 | class Parser |
Better:
1 | template <typename T> |
Reasoning:
To constraint the parameters, we can use Runtime checks and polymorphism.
Yet it also bind us to specific types.
Concepts constraint our parameters through the means of generic types.
This would benefit us when we are passing the “wrong” object:
Let’s assume we made a typo in the function:
1 | template <typename T> |
When using the function FuncNoConstraint
we get the error in the function itself:
1 | Error C2039 'Parse': is not a member of 'A' TestingCPPApp |
When using the function FuncWithConstraint
we get a specific error in the right location:
1 | Error C2672 'FuncWithConstraint': no matching overloaded function found TestingCPPApp |
C++ is a powerful language with so much to learn :)
Thanks for reading!