Unique Pointer
Basics
1
2
#include <memory>
std::unique_ptr<int> ptr = std::make_unique<int>(1);
- Note that a
unique_ptris only8 byteson a 64 bit system, and it’s the same size as a raw pointer, because it is just a wrapper that ensures sole ownership of the pointer.
Shared Pointer
Memory Usage of Shared Pointer
Note that a shared_ptr is 2x8=16 bytes itself. It includes:
- a raw pointer
- a pointer to the control block.
The control block has a reference counter, deleter, etc. Its implementation is platform-dependent. Roughly, we need 8 bytes for the count, and 8 bytes for the pointer to the deleter. So, 16 bytes.
Can you see the difference here?
1
2
3
auto ptr = std::make_shared<Type> (args);
// vs
auto ptr2 = std::shared_pointer<Type> (new Type());
ptrhas 1 memory allocation call: control block + the object itselfptr2has 2 memory allocation calls: control block and the object itself separately
Weak Pointer
Usage: when two pointers could point to each other (cyclic reference) . Wrong example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student {
shared_ptr<Student> bestFriend;
public:
void makeFriend(shared_ptr<Student> other) {
bestFriend = other;
}
};
auto tom = make_shared<Student>();
auto jerry = make_shared<Student>();
tom->makeFriend(jerry); // tom拉住jerry
jerry->makeFriend(tom); // jerry也拉住tom
// Cyclic memory free!
shared_ptrincrements reference counts of each other’s members. So this will increase each student’s count. So the rule of thumb is use the weak pointer in a class which stores a pointer to another instance of the same class!
Correct version:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <memory>
class Student {
std::weak_ptr<Student> bestFriend; // non-owning reference
public:
void makeFriend(const std::shared_ptr<Student>& other) {
bestFriend = other;
}
std::shared_ptr<Student> getBestFriend() const {
return bestFriend.lock(); // may be nullptr if expired
}
};
int main() {
auto tom = std::make_shared<Student>();
auto jerry = std::make_shared<Student>();
tom->makeFriend(jerry);
jerry->makeFriend(tom);
// No cycle of ownership: objects are freed normally.
}
Common Operations
Reset: std::unique_ptr::reset() and std::shared_ptr::reset
unique_ptr::reset()is basically basicallydelete ptr; ptr = nullptrstd::shared_ptr::reset()can be a bit slower, because of deallocation & allocation of referecen count.
1
2
3
4
5
std::unique_ptr<int> ptr;
ptr.reset(new int(1));
std::shared_ptr<int> s_ptr;
s_ptr = std::make_shared<int>(3);
s_ptr.reset(new int(3));
Ownership:
unique_ptris move-assigned.
1
2
3
4
5
6
// ownership
// unique_ptr is move-assigned
std::unique_ptr<int> new_ptr = std::make_unique<int>(5);
// If the count reaches 0, it also deletes the object and the control block
std::shared_ptr<int> s_ptr2 = std::make_shared<int>(6);
s_ptr = s_ptr2; // now the element 3 is deleted since it's not referenced
Conversions
unique_ptr -> shared_ptr: use std::move to transfer ownership
1
2
3
4
5
std::unique_ptr<Foo> u = std::make_unique<Foo>();
std::shared_ptr<Foo> s = std::move(u); // OK
// or:
std::shared_ptr<Foo> s2(std::move(u)); // OK
share_from_this
When you want to pass this as a pointer to another object, you must use share_from_this()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <functional>
struct Worker : std::enable_shared_from_this<Worker> {
void startAsyncJob() {
auto self = shared_from_this();
std::thread([self] {
self->onJobDone();
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
}).detach();
}
void onJobDone() {
std::cout << "Job done, Worker is still alive.\n";
}
~Worker() {
std::cout << "Worker destroyed.\n";
}
};
int main() {
auto w = std::make_shared<Worker>();
w->startAsyncJob();
w.reset();
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
}
- Note, in this example,
w.reset()would NOT call the destructor because the detached thread now owns Worker. So,Worker destroyedis called when the worker function is done.
Caveat: shared_from_this cannot be used in constructor, because shared_ptr won’t be ready yet
Instead, use a reference to the object
1
2
3
Foo::Foo() {
Foo& r = *this; // always valid
}