smart pointers

38
Smart->Pointers*

Upload: vishal-mahajan

Post on 13-Jul-2015

84 views

Category:

Software


3 download

TRANSCRIPT

Smart->Pointers*

Smart Pointers

Introduction

RAII

What are they ?

Examples of Smart Pointers

Benefits of Smart Pointers

Introduction

In programming, the use of pointers are the main source of

errors (or bugs) when developing code.

The main problem found are the occurrences of memory

leaks, this is due to the way pointers interact with memory,

such as allocation and deallocation, which when

performed inefficiently can cause the pointer to hang (or

dangle, meaning that the pointer points to a previous

removed object).

The solution to this problem is the use of Smart Pointers.

RAII

This is a programming idiom, In RAII, holding a

resource is tied to object lifetime: resource

allocation (acquisition) is done during object

creation (specifically initialization), by the

constructor, while resource deallocation (release)

is done during object destruction, by the

destructor. If objects are destructed properly,

resource leaks do not occur.

Goal of RAII

The main goal of this idiom is->

1] To ensure that resource acquisition occurs at the same

time that the object is initialized, so that all resources for

the object are created and made ready in one line of

code.

2] The resource is automatically freed when the object

gets out of scope.

RAII

Release Resource

Use Resource

Obtain Resource

Smart pointers

Smart pointers are crucial to the RAII or Resource Acquisition Is

Initialialization programming idiom.

Smart pointers are class objects that behave like built-in pointers.

They also support pointer operations like dereferencing (operator *)

and indirection (operator ->).

To be smarter than regular pointers, smart pointers need to do things

that regular pointers don't. What could these things be? Probably the

most common bugs in C++ (and C) are related to pointers and

memory management: dangling pointers, memory leaks, allocation

failures and other joys. Having a smart pointer take care of these things can save a lot of aspirin..

Smart pointers

1] std::auto_ptr

2] std::unique_ptr [c++ 11]

3] boost::shared_ptr

4] std::shared [c++ 11]

5] boost::scoped_ptr

6] boost ::weak_ptr

It is common to write code such

as..

void myFunction()

{

myClass *p (new myClass());

p->Dosomething();

delete p;

}

Example of smart pointers

This code may work. But what if somewhere in the function body an

exception gets thrown? Suddenly, the delete code never gets called!

In this case a memory leak waiting to happen.

However the use of a smart pointer will remove this threat due to the

automatic clean up of the pointer because the pointer will be cleaned

up whenever it gets out of scope, whether it was during the normal

path of execution or during an exception.

Auto_ptr: the simplest smart pointer to use. For situations when there

are no special requirements.

auto_ptr

auto_ptr is a class template available in the C++ Standard Library (declared in the <memory> header file) that provides some basic RAII features for C++ raw pointers.

The auto_ptr template class describes an object that stores a pointer to a single allocated object of type Type* that ensures that the object to which it points gets destroyed automatically when control leaves a scope.

auto_ptr

void myFunction()

{

auto_ptr<myClass> *p (new myClass());

p->DoSomething();

//delete obj; /*memory allocated will

automatically be freed*/

}

An Example of Smart Pointers

void foo()

{

MyClass* p(new

MyClass);

p->DoSomething();

delete p;

}

void foo()

{

auto_ptr<MyClass>

p(new MyClass);

p->DoSomething();

}

auto_ptr

The auto_ptr has semantics of strict ownership, meaning that the

auto_ptr instance is the sole entity responsible for the object's lifetime. If

an auto_ptr is copied, the source loses the reference. For example:

MyClass* p(new MyClass);

MyClass* q = p;

delete p;

p->DoSomething(); // Watch out! p is now dangling!

p = NULL; // p is no longer dangling

q->DoSomething(); // q is still dangling!

An Example of Smart Pointers

For auto_ptr, this is solved by setting its pointer to NULL when it is copied:

int main(int argc, char **argv)

{

int *i = new int;

auto_ptr<int> x(i);

auto_ptr<int> y;

y = x;

cout << x.get() << endl; // Print NULL

cout << y.get() << endl; // Print non-NULL address i

An Example of Smart Pointers

This code will print a NULL address for the first

auto_ptr object and some non-NULL address for

the second, showing that the source object lost

the reference during the assignment (=). The raw

pointer i in the example should not be deleted, as

it will be deleted by the auto_ptr that owns the

reference. In fact, new int could be passed

directly into x, eliminating the need for i.

#include <memory> // for std::auto_ptr

#include <stdlib.h> // for EXIT_SUCCESS

using namespace std;

typedef struct { int a, b; } IntPair;

int main(int argc, char **argv) {

auto_ptr<int> x(new int(5));

// Return a pointer to the pointed-to object.

int *ptr = x.get();

// Return a reference to the value of the pointed-to object.

int val = *x;

// Access a field or function of a pointed-to object.

auto_ptr<IntPair> ip(new IntPair);

ip->a = 100;

// Reset the auto_ptr with a new heap-allocated object.

x.reset(new int(1));

// Release responsibility for freeing the pointed-to object.

ptr = x.release();

delete ptr;

return EXIT_SUCCESS;

auto_ptr details…

template <class T> class auto_ptr

{

T* ptr;

public:

explicit auto_ptr(T* p = 0) : ptr(p) {}

~auto_ptr() {delete ptr;}

T& operator*() {return *ptr;}

T* operator->() {return ptr;}

};

auto_ptr details…

template <class T>

auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<T>& rhs)

{

if (this != &rhs)

{

delete ptr;

ptr = rhs.ptr;

rhs.ptr = NULL;

}

return *this;

}

Problem with auto_ptr

The C++ Standard says that an STL element must be "copy-constructible" and "assignable." In other words, an element must be able to be assigned or copied and the two elements are logically independent. std::auto_ptr does not fulfill this requirement. For example-

void foo() {

vector<auto_ptr< > > ivec;

ivec.push_back(auto_ptr< >(new (5)));

ivec.push_back(auto_ptr< >(new (6)));

auto_ptr< > z = ivec[0];

}

Problem with auto_ptr

To overcome this limitation, you should use

the std::unique_ptr, std::shared_ptr or

std::weak_ptr smart pointers or the boost

equivalents if you don't have C++11.

shared_ptr

Shared pointer is a smart pointer (a C++ object wihoverloaded operator*() and operator->())

It keeps a pointer to an object and a pointer to a shared reference count.

Every time a copy of the smart pointer is made using the copy constructor, the reference count is incremented.

When a shared pointer is destroyed, the reference count for its object is decremented.

After counts goes to zero then managed object automatically get deleted.

int main(int argc, char **argv) {

// x contains a pointer to an int and has reference count 1.

boost::shared_ptr<int> x(new int(10));

{

// x and y now share the same pointer to an int, and they

// share the reference count; the count is 2.

boost::shared_ptr<int> y = x;

std::cout << *y << std::endl;

}

// y fell out of scope and was destroyed. Therefore, the

// reference count, which was previously seen by both x and y,

// but now is seen only by x, is decremented to 1.

return EXIT_SUCCESS;

}

Finally, something that works!

it is safe to store shared_ptrs in containers, since

copy/assign maintain a shared reference count and

pointer-

bool sortfunction(shared_ptr<int> x, shared_ptr<int> y) {

return *x < *y;

}

bool printfunction(shared_ptr<int> x) {

std::cout << *x << std::endl;

}

int main(int argc, char **argv) {

vector<shared_ptr<int> > vec;

vec.push_back(shared_ptr<int>(new int(9)));

vec.push_back(shared_ptr<int>(new int(5)));

vec.push_back(shared_ptr<int>(new int(7)));

std::sort(vec.begin(), vec.end(), &sortfunction);

std::for_each(vec.begin(), vec.end(), &printfunction);

return EXIT_SUCCESS;

}

How they work

The process starts when the managed object is dynamically allocated,

and the first shared_ptr (sp1) is created to point to it; the shared_ptr

constructor creates a manager object (dynamically allocated). The manager object contains a pointer to the managed object; the

overloaded member functions like shared_ptr::operator-> access the

pointer in the manager object to get the actual pointer to the

managed object.1 The manager object also contains two reference

counts: The shared count counts the number of shared_ptrs pointing to

the manager object, and the weak count counts the number of

weak_ptrs pointing to the manager object. When sp1 and the

manager object are first created, the shared count will be 1, and the weak count will be 0.

How they work…

shared_ptr

manager object

managed object

Pointer

Shared count- 3

Weak count- 2

sp1

sp2

sp3

wp1 wp2

How they work….

If another shared_ptr (sp2) is created by copy or assignment from sp1,

then it also points to the same manager object, and the copy

constructor or assignment operator increments the shared count to show that 2 shared_ptrs are now pointing to the managed object.

Likewise, when a weak pointer is created by copy or assignment from

a shared_ptr or another weak_ptr for this object, it points to the same

manager object, and the weak count is incremented. The diagram

shows the situation after three shared_ptrs and two weak_ptrs have

been created to point to the same object.

Problem with shared_ptr

If you used shared_ptr and have a cycle in the

sharing graph, the reference count will never hit

zero.

cycle of shared_ptr’s

#include <boost/shared_ptr.hpp>

boost::shared_ptr;

A {

shared_ptr<A> next;

shared_ptr<A> prev;

};

int main(int argc, char **argv) {

shared_ptr<A> head( A());

head->next = shared_ptr<A>( A());

head->next->prev = head;

}

2

0

2

1

0

next

prev

next

prev

head

breaking the cycle with weak_ptr

A {

shared_ptr<A> next;

weak_ptr<A> prev;

};

int main(int argc, char **argv) {

shared_ptr<A> head(new A());

head->next = shared_ptr<A>(new A());

head->next->prev = head;

}

1

0

1

1

0

next

prev

next

prev

head

#include <boost/shared_ptr.hpp>

#include <boost/weak_ptr.hpp>

#include <iostream>

int main(int argc, char **argv) {

boost::weak_ptr<int> w;

{

boost::shared_ptr<int> x;

{

boost::shared_ptr<int> y(new int(10));

w = y;

x = w.lock();

std::cout << *x << std::endl;

}

std::cout << *x << std::endl;

}

boost::shared_ptr<int> a = w.lock();

std::cout << a << std::endl;

return 0;

}

use of make_shared to create an object

If you need to create an object using a custom allocator, you can use

make_shared. So, why use make_shared ? There are two main reasons:

simplicity, and efficiency.

First, with make_shared the code is simpler. Write for clarity and

correctness first.

Second, using make_shared is more efficient.

The shared_ptr implementation has to maintain housekeeping

information in a control block shared by all shared_ptrs

and weak_ptrs referring to a given object. In particular, that

housekeeping information has to include not just one but two reference counts:

use of make_shared to create an object

A “strong reference” count to track the number

of shared_ptrs currently keeping the object alive.

A “weak reference” count to track the number

of weak_ptrs currently observing the object.

Example-

sp1 = shared_ptr<widget>{ new widget{} };

sp2 = sp1

use of make_shared to create an object

use of make_shared to create an object

We’d like to avoid doing two separate allocations

here. If you use make_shared to allocate the

object and the shared_ptr all in one go, then the

implementation can fold them together in a single

allocation, as shown in Example-

sp1 = make_shared<widget>();

sp2 = sp1;

use of make_shared to create an object

use of make_shared to create an object

It reduces allocation overhead, including

memory fragmentation.

It improves locality. The reference counts

are frequently used with the object, and

for small objects are likely to be on the

same cache line, which improves cache

performance.