Skip to content

Latest commit

 

History

History
192 lines (135 loc) · 7.21 KB

File metadata and controls

192 lines (135 loc) · 7.21 KB

Best practices


Best practices

  • Rule of 0, Rule of 5
  • Avoid explicit new
  • Use std::make_shared() / std::make_unique()
  • Copying std::shared_ptr<>
  • Use references instead of pointers

Rule of 0, Rule of 5

Rule of 5

  • If you need to implement one of those functions:
    • destructor
    • copy constructor
    • copy assignment operator
    • move constructor
    • move assignment operator
  • It probably means that you should implement them all, because you have manual resources management.

Rule of 0

  • If you use RAII wrappers on resources, you don’t need to implement any of Rule of 5 functions.

Avoid explicit new

  • Smart pointers eliminate the need to use delete explicitly
  • To be symmetrical, do not use new as well
  • Allocate using:
    • std::make_unique()
    • std::make_shared()

Use std::make_shared() / std::make_unique()

  • What is a problem here?
struct MyData { int value; };
using Ptr = std::shared_ptr<MyData>;
void sink(Ptr oldData, Ptr newData);

void use(void) {
    sink(Ptr{new MyData{41}}, Ptr{new MyData{42}});
}
  • Hint: this version is not problematic
struct MyData { int value; };
using Ptr = std::shared_ptr<MyData>;
void sink(Ptr oldData, Ptr newData);

void use(void) {
    Ptr oldData{new MyData{41}};
    Ptr newData{new MyData{42}};
    sink(std::move(oldData), std::move(newData));
}

Allocation deconstructed

auto p = new MyData(10); means:

  • allocate sizeof(MyData) bytes
  • run MyData constructor
  • assign address of allocated memory to p

The order of evaluation of operands of almost all C++ operators (including the order of evaluation of function arguments in a function-call expression and the order of evaluation of the subexpressions within any expression) is unspecified.


Unspecified order of evaluation

  • How about two such operations?
first operation (A) second operation (B)
(1) allocate sizeof(MyData) bytes (1) allocate sizeof(MyData) bytes
(2) run MyData constructor (2) run MyData constructor
(3) assign address of allocated memory to p (3) assign address of allocated memory to p
  • Unspecified order of evaluation means that order can be for example:
    • A1, A2, B1, B2, C3, C3
  • What if B2 throws an exception?

Use std::make_shared() / std::make_unique()

  • std::make_shared() / std::make_unique() resolves this problem
struct MyData{ int value; };
using Ptr = std::shared_ptr<MyData>;
void sink(Ptr oldData, Ptr newData);

void use() {
    sink(std::make_shared<MyData>(41), std::make_shared<MyData>(42));
}
  • Fixes previous bug
  • Does not repeat a constructed type
  • Does not use explicit new
  • Optimizes memory usage (only for std::make_shared())

Copying std::shared_ptr<>

void foo(std::shared_ptr<MyData> p);

void bar(std::shared_ptr<MyData> p) {
    foo(p);
}
  • requires counters incrementing / decrementing
  • atomics / locks are not free
  • will call destructors
Can be better?

Copying std::shared_ptr<>

void foo(const std::shared_ptr<MyData> & p);

void bar(const std::shared_ptr<MyData> & p) {
    foo(p);
}
  • as fast as pointer passing
  • no extra operations
  • not safe in multithreaded applications

Use references instead of pointers

  • What is the difference between a pointer and a reference?
    • reference cannot be empty
    • reference, once assigned cannot point to anything else
  • Priorities of usage (if possible):
    • (const) T&
    • std::unique_ptr<T>
    • std::shared_ptr<T>
    • T*

Exercise: List

Take a look at List.cpp file, where simple (and buggy) single-linked list is implemented.

  • void add(Node* node) method adds a new Node at the end of the list.
  • Node* get(const int value) method iterates over the list and returns the first Node with matching value or nullptr
  1. Compile and run List application
  2. Fix memory leaks without introducing smart pointers
  3. Fix memory leaks with smart pointers. What kind of pointers needs to be applied and why?
  4. (Optional) What happens when the same Node is added twice? Fix this problem.
  5. (Optional) Create EmptyListError exception (deriving from std::runtime_error). Add throwing and catching it in a proper places.