smart pointer

This post will introduce two types of smart pointers, which can help programmers to manage objects allocated in heap.

unique_ptr

unique_ptr can manage another object through a pointer and dispose of that object when the unique_ptr goes out of scope. unique_ptr ensure that there is only one smart pointer points to the object.

The object is disposed of using the assiciated deleter when either fo the following happens:

  • the managing unique_ptr object is destoryed
  • unique_ptr object is assigned to another pointer via operator= or reset()

The object can be disposed of using a user-supplied deleter by calling get_delter()(ptr).

unique_ptr can manage a single object or a dynamically-allocated array of objects(allocated by new[]). For the latter, when the object is disposed, unique_ptr will call delete[] to distory the array of objects defautly.

unique_ptr
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <vector>
#include <memory>
#include <cstdio>
#include <fstream>
#include <cassert>
struct B {
virtual void bar() { std::cout << "B::bar\n"; }
virtual ~B() = default;
};
struct D : B {
D() { std::cout << "D::D\n"; }
~D() { std::cout << "D::~D\n"; }
void bar() override { std::cout << "D::bar\n"; }
};
// a function consuming a unique_ptr can take it by value or by rvalue reference
std::unique_ptr<D> pass_through(std::unique_ptr<D> p) {
p->bar();
return p;
}
int main() {
// unique ownership semantics
{
auto p = std::make_unique<D>(); // p is a unique_ptr that owns a D
auto q = pass_through(std::move(p));
assert(!p); // now p owns nothing and holds a null pointer
q->bar(); // and q owns the D object
} // ~D called here
// Runtime polymorphism demo
{
std::unique_ptr<B> p = std::make_unique<D>(); // p is a unique_ptr that owns a D
// as a pointer to base
p->bar(); // virtual dispatch
std::vector<std::unique_ptr<B>> v; // unique_ptr can be stored in a container
v.push_back(std::make_unique<D>());
v.push_back(std::move(p)); // notice: v.push_back(p) causes compiler errors
v.emplace_back(new D);
for(auto& p: v) p->bar(); // virtual dispatch
} // ~D called 3 times
// Custom lambda-expression deleter
{
std::unique_ptr<D, std::function<void(D*)>> p(new D, [](D* ptr){
std::cout << "destroying from a custom deleter...\n";
delete ptr;
}); // p owns D
p->bar();
} // the lambda above is called and D is destroyed
//Array form of unique_ptr demo
{
std::unique_ptr<D[]> p{new D[3]};
} // calls ~D 3 times
}

get() and release() can return the raw pointer managed by unique_ptr object.release() will release the unique_ptr object from responsibility of deleting the managed object, but get() will not.

get() and release()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// foo bar p
// --- --- ---
std::unique_ptr<int> foo; // null
std::unique_ptr<int> bar; // null null
int* p = nullptr; // null null null
foo = std::unique_ptr<int>(new int(10)); // (10) null null
bar = std::move(foo); // null (10) null
p = bar.get(); // null (10) (10)
*p = 20; // null (20) (20)
p = nullptr; // null (20) null
foo = std::unique_ptr<int>(new int(30)); // (30) (20) null
p = foo.release(); // null (20) (30)
*p = 40; // null (20) (40)
delete p; // the program is now responsible of deleting the object pointed to by p
// bar deletes its managed object automatically

shared_ptr

shared_ptr is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr object may own the same object. The object is destoryed when either of following happens:

  • the last remaining shared_ptr owning the object is destoryed.
  • the last remaining shared_ptr owning the object is assigned another one via operator= or reset().

A typical shared_ptr holds two pointers:

  • the stored pointer (return by get()).
  • a pointer to control block.
    The control block pointers to a object that holds the number of shared_ptr who point to the managed object.

A poor sample for shared_ptr implementation:

sample for shared_ptr
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
31
32
33
34
35
36
37
38
39
40
41
42
template <typename T>
class SmartPtr; // declare a template class for friend class.
template <typename T>
class U_Ptr { // control block
private:
friend class SmartPtr<T>; // private friend class which can access members of control block.
// all following are private means they can be called by friend class only.
U_Ptr(T *ptr) :p(ptr), count(1) { }
~U_Ptr() { delete p; }
int count; // counter for number of shared_ptr that points to the same object.
// points to managed object.
T *p;
};
template <typename T>
class SmartPtr { // smart pointer class
public:
SmartPtr(T *ptr) : rp(new U_Ptr<T>(ptr)) { } // contructor makes control block to manage object.
SmartPtr(const SmartPtr<T> &sp) :rp(sp.rp) { ++rp->count; } // copy constructor
SmartPtr& operator=(const SmartPtr<T>& rhs) { // assignment operator
++rhs.rp->count;
if (--rp->count == 0)
delete rp;
rp = rhs.rp;
return *this;
}
T& operator*() { // overload * operator.
return *(rp->p);
}
T* operator ->() { // overload -> operator.
return rp->p;
}
~SmartPtr() { // distructor
if (--rp->count == 0) // when counter is 0, delete control block which lead to distruct managed object.
delete rp;
}
private:
U_Ptr<T> *rp; // pointer to control block.
};