Smart Pointers
Slide ShowSmart Pointers
Contents
- What is a smart pointer
std::auto_ptr
std::unique_ptr
- reference counting
std::shared_ptr
- other smart pointers
What is a smart pointer
Object that behaves as a pointer by overloading operator*()
and operator->()
// the simplest SmartPointer
template <typename T>
class SmartPointer
{
public:
T* operator->()
{
return m_Pointer;
}
T& operator* ()
{
return *m_Pointer;
}
private:
T* m_Pointer;
};
operator->()
Every time the compiler evaluates the ->
operator, until the right
side of the operator becomes a native pointer.
What are smart pointers useful for?
- Managing resources
- Expressing ownership semantic
Standard smart pointers
std::auto_ptr
std::unique_ptr
std::shared_ptr
std::weak_ptr
boost::intrusive_ptr
std::auto_ptr
A smart pointer that frees the memory upon destruction. In C++ 11 it was
removed from the language. It is fully replaced by std::unique_ptr
without any drawbacks
A simplified implementation of std::auto_ptr
The "smart pointer" part
template <typename T>
class auto_ptr {
// ...
T* operator->()
{
return m_Pointer;
}
T& operator* ()
{
return *m_Pointer;
}
// ...
T* m_Pointer;
};
The RAII part
template <typename T>
class auto_ptr {
// ...
auto_ptr()
: m_Pointer(nullptr)
{}
auto_ptr(T* pointer)
: m_Pointer(pointer)
{}
~auto_ptr()
{
delete m_Pointer;
}
// ...
T* m_Pointer;
};
The bad parts
template <typename T>
class auto_ptr {
// ...
// copy ctor changes the original!!!
auto_ptr(auto_ptr& o) : m_Pointer(o.m_Pointer) {
o.m_Pointer = nullptr;
}
// assignment changes the original!!!
auto_ptr& operator=(auto_ptr& rhs) {
if (this != &rhs) {
delete m_Pointer;
m_Pointer = rhs.m_Pointer;
rhs.m_Pointer = nullptr;
}
return *this;
}
// ...
T* m_Pointer;
};
Due to the lack of move semantics in C++ 98, std::auto_ptr
is forced
to transfer the ownership of the pointer during copy construction and
assignment. std::auto_ptr
was also copiable and assignable. You could
make a std::vector<std::auto_ptr>
, but it would be completely
unpredictable - the standard does not require certain copy/assignment
guarantees for containers.
std::unique_ptr
C++ 11 introduces std::unique_ptr
- it is not copyable
- it is moveable
A simplified implementation of std::unique_ptr
The "smart pointer" part
template <typename T>
struct unique_ptr {
// ...
T* operator->()
{
return m_Pointer;
}
T& operator* ()
{
return *m_Pointer;
}
// ...
T* m_Pointer;
};
The RAII part
template <typename T>
struct unique_ptr {
// ...
unique_ptr()
: m_Pointer(nullptr)
{}
unique_ptr(T* pointer)
: m_Pointer(pointer)
{}
~unique_ptr()
{
delete m_Pointer;
}
// ...
T* m_Pointer;
};
The non-copyable part
template <typename T>
struct unique_ptr {
// ...
unique_ptr(const unique_ptr& o) = delete;
unique_ptr& operator= (const unique_ptr& rhs) = delete;
// ...
T* m_Pointer;
};
The move part
template <typename T>
struct unique_ptr {
// ...
unique_ptr(unique_ptr&& o)
: m_Pointer(o.m_Pointer)
{
o.m_Pointer = nullptr;
}
unique_ptr& operator=(unique_ptr&& rhs)
{
if (this != &rhs) {
delete m_Pointer;
m_Pointer = rhs.m_Pointer;
rhs.m_Pointer = nullptr;
}
return *this;
}
// ...
T* m_Pointer;
};
std::unique_ptr<T>
synopsis
unique_ptr()
default constructorunique_ptr(Y*)
constructor - allows using pointers toDerived
classes to be hold instd::unique_ptr<Base>
T& operator* ()
T* operator-> ()
const T& operator* () const
const T* operator-> () const
T* get()
- get the underlying pointerconst T* get() const
-
T* release()
- get the underlying pointer and release ownership over it. Use it when you need to give up the ownership of the resource -
void reset(T* new_pointer = nullptr)
- reset the underlying pointer tonew_pointer
and get ownership over it. Deletes the current pointer.
explicit operator bool()
allows usage such as
std::unique_ptr<T> p;
if (p) {
std::cout << *p << std::endl;
}
std::unique_ptr
has a second template parameter
template <typename T, typename Deleter = std::default_delete<T>>
class unique_ptr
{
~unique_ptr() {
auto deleter = get_deleter();
deleter(m_Pointer);
}
};
Bad
std::unique_ptr<Lifetime> a(new Lifetime[2]);
//std::cout << "Second: " << &a[1] << std::endl;
Will make a delete
to memory allocated with new []
Good
std::unique_ptr<Lifetime[]> a(new Lifetime[2]);
std::cout << "Second: " << &a[1] << std::endl;
T& operator[](size_t index)
- allows array element access. Only
std::unique_ptr<T[]>
has this method.
Custom deleter
struct ReleaseDelete {
template <typename T>
void operator()(T* pointer) {
pointer->Release();
}
};
// ...
std::unique_ptr<ID3D11Device, ReleaseDelete> device;
Will be back to unique_ptr
deleter when we get to:
- template typedefs
- template specialization
std::unique_ptr
owns the resource it is pointing to
Use this to express ownership relation between types
std::shared_ptr
std::shared_ptr
allows sharing of a resource. It is freed when the
last std::shared_ptr
instance pointing to the resource is destroyed.
std::shared_ptr
uses Reference
Counting.
Reference counting
A technique of storing the number of references, pointers, or handles to a resource.
A garbage collection algorithm that uses these reference counts to deallocate objects which are no longer referenced
Reference counting
Reference counting
std::shared_ptr<int> t(new int(42));// pointer 0xff00 : rc 1
std::shared_ptr<int> q = t; // pointer 0xff00 : rc 2
std::shared_ptr<int> p(new int(24));// pointer 0xfe00 : rc 1
t = p; // pointer 0xff00 : rc 1
// pointer 0xfe00 : rc 2
q = t; // pointer 0xff00 : rc 0, so it is deleted
// pointer 0xfe00 : rc 3
Implementation of std::shared_ptr
The reference count and the resource need to be shared between all instances. So they have to be on the heap.
SharedPtr()
: m_RC(nullptr)
, m_Pointer(nullptr)
{}
SharedPtr(T* pointer)
: m_RC(new int(1))
, m_Pointer(pointer)
{
}
// ...
~SharedPtr()
{
RemoveReference();
}
// ...
int* m_RC;
T* m_Pointer;
void RemoveReference()
{
if (m_RC && --*m_RC == 0)
{
delete m_Pointer;
delete m_RC;
}
}
void AddReference()
{
++*m_RC;
}
SharedPtr(const SharedPtr& o)
: m_RC(o.m_RC)
, m_Pointer(o.m_Pointer)
{
AddReference();
}
SharedPtr& operator=(const SharedPtr& rhs)
{
if (this != &rhs)
{
RemoveReference();
m_RC = rhs.m_RC;
m_Pointer = rhs.m_Pointer;
AddReference();
}
return *this;
}
SharedPtr(SharedPtr&& o)
: m_RC(o.m_RC)
, m_Pointer(o.m_Pointer)
{
o.m_RC = nullptr;
o.m_Pointer = nullptr;
}
SharedPtr& operator=(SharedPtr&& rhs)
{
if (this != &rhs) {
RemoveReference();
m_RC = rhs.m_RC;
m_Pointer = rhs.m_Pointer;
rhs.m_RC = nullptr;
rhs.m_Pointer = nullptr;
}
return *this;
}
shared_ptr
is for sharing
Use a shared_ptr
to express that the ownership of the resource is shared.
Reference Counting and cycles
If you have a cycle in the references between resources in
std::shared_ptr
, these resources will never be freed.
How to handle cyclic references?
- manually by breaking the cycle
- use weak references
std::weak_ptr
Weak references point to a resource, but do not prolong its lifetime.
std::weak_ptr<int> w;
{
std::shared_ptr<int> s = std::make_shared<int>(42);
w = s;
}
if (auto pointer = w.lock()) {
std::cout << *pointer << std::endl;
} else {
std::cout << "It's gone" << std::endl;
}
template <typename T>
class weak_ptr {
// ...
shared_ptr lock() {
if (<It is still alive>) {
return shared_ptr<T>(<for It>);
}
return shared_ptr();
}
};
So how does shared_ptr
work?
Where is the reference count stored?
- as a member in the
shared_ptr
? - as a static member in
shared_ptr<T>
?
As a member?
std::shared_ptr<int> q;
{
std::shared_ptr<int> p = std::make_shared<int>(42);
q = p;
// p gets destroyed here, but it can't reach within q
// unless an expensive list of shared_ptr is stored
// somewhere
}
As a static member?
std::shared_ptr<int> p = std::make_shared<int>(42);
std::shared_ptr<int> q = std::make_shared<int>(42);
// oops, p and q have reference count 2, but actually point to
// different resources
In the heap as a shared object
shared_ptr
price
- double indirection
- can be optimized with a little higher memory usage
- extra memory allocation
- worse - it is a small memory allocation, which are most wasteful
Use std::make_shared
- allocates a single block for object and reference count
- the object is inplace constructed in the block
- better cache locality
Intrusive reference counting
The reference count is stored in the resource (object) itself.
boost::intrusive_ptr
-
add a reference - calls
intrusive_ptr_add_ref(T*)
-
release a reference - calls
intrusive_ptr_release(T*)
class Renderer;
void intrusive_ptr_add_ref(Renderer* r) { ++r->refs; }
void intrusive_ptr_release(Renderer* r) { if (--r->refs == 0) { delete r; } }
When to use intrusive_ptr
?
- some external APIs are already reference counted
- OS, other languages
- more memory efficient than
shared_ptr
std???::intrusive_ptr
There is no std::intrusive_ptr
class, because std::make_shared
gives you
almost the same thing.
Almost?
Using an intrusive_ptr
allows you to temporary give up
using smart pointers, go down to C
pointer and then go up
to an intrusive_ptr
std::enable_shared_from_this<T>
- allows creating a
shared_ptr
fromthis
. - requires that there is at least one shared_ptr pointing to the object
- works by having a
std::weak_ptr
inside the object for the object itself
class Renderer : public std::enable_shared_from_this<Renderer>
{
};
std::shared_ptr<Renderer> get_the_shared_ptr(Renderer* r)
{
return r->shared_from_this();
}
No plain new
std::make_shared<T>
is the best way to create a shared pointerC++
14 -std::make_unique<T>
- creates an unique pointer
Other smart pointers
- Facebook log and leak
- use 15 counts and when it reaches 16, log and leave it to leak
link_ptr