- 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
-
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.
- If you use RAII wrappers on resources, you don’t need to implement any of Rule of 5 functions.
-
Smart pointers eliminate the need to use
deleteexplicitly -
To be symmetrical, do not use
newas well -
Allocate using:
-
std::make_unique() -
std::make_shared()
-
- 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));
}auto p = new MyData(10); means:
-
allocate
sizeof(MyData)bytes -
run
MyDataconstructor -
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.
- 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?
-
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())
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
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
-
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*
-
Take a look at List.cpp file, where simple (and buggy) single-linked list is implemented.
void add(Node* node)method adds a newNodeat the end of the list.Node* get(const int value)method iterates over the list and returns the first Node with matchingvalueornullptr
- Compile and run List application
- Fix memory leaks without introducing smart pointers
- Fix memory leaks with smart pointers. What kind of pointers needs to be applied and why?
- (Optional) What happens when the same Node is added twice? Fix this problem.
-
(Optional) Create
EmptyListErrorexception (deriving fromstd::runtime_error). Add throwing and catching it in a proper places.