0% found this document useful (0 votes)
67 views

Smart Pointers

A Smart Pointer is basically an abstract data type that simulates a pointer while providing additional features, such as automatic garbage collection or bounds checking. To look and behave like pointers, Smart Pointers need to have the same interface that pointers have i.e. They need to support pointer operations like dereferencing (operator ->) Smart Pointers must take care of most common bugs related to pointers and memory management like dangling pointers, memory leaks, allocation failures etc.

Uploaded by

rupeshk_p
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
67 views

Smart Pointers

A Smart Pointer is basically an abstract data type that simulates a pointer while providing additional features, such as automatic garbage collection or bounds checking. To look and behave like pointers, Smart Pointers need to have the same interface that pointers have i.e. They need to support pointer operations like dereferencing (operator ->) Smart Pointers must take care of most common bugs related to pointers and memory management like dangling pointers, memory leaks, allocation failures etc.

Uploaded by

rupeshk_p
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 6

Smart Pointers

Rupesh KP

Smart Pointers 1
Smart Pointers 2
What is a Smart Pointer?
• A smart pointer is basically an abstract data type that simulates a pointer while
providing additional features, such as automatic garbage
collection or bounds checking. These additional features are intended to reduce bugs caused by the misuse of pointers while retaining efficiency.
Smart pointers typically keep track of the objects they point to for the purpose of memory management.

• Smart pointers are objects that look and behave like pointers, but are smarter.
To look and behave like pointers, smart pointers need to have the same interface that pointers have i.e. they need to 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 do i.e. it must take care of most common bugs
related to pointers and memory management like dangling pointers, memory leaks, allocation failures etc.
NOTE: The (->) operator works same as (.) operator but (.) oprator can't be overloaded while (->) can be overloaded.

• Some times it is necessary to use something automatic instead of classical C++


pointers. For
example you want that your pointer is automatically deleted. For this we need a class that automates these things. Such class is called Smart
Pointer.

Why do we need Smart Pointers?


The misuse of pointers like the constant allocation, deallocation introduces the risk of memory leaks and is a major source of bugs.
Smart pointers prevent memory leaks by making the resource deallocation automatic i.e. when the pointer (or the last in a series of pointers) to
an object is destroyed, for example because it goes out of scope, the pointed object is destroyed too.
There are many types of smart pointers and different smart pointers offer different reasons for use. Here are some common reasons for using
smart pointers in C++.
• Automatic cleanup: Using smart pointers that clean after themselves can save a few lines of code and also help in reducing the
probability for bugs like you don't need to remember to free the pointer.

• Automatic initialization. Another nice thing is that you don't need to initialize the pointer to NULL, since the default constructor does
that for you.
• Dangling pointers. A common pitfall of regular pointers is the dangling pointer i.e. a pointer that points to an object that is already
deleted. The following code illustrates this situation:
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(); // Ouch! q is still dangling!

For auto_ptr, this is solved by setting its pointer to NULL when it is copied:
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;
}
• Other smart pointers may do other things when they are copied. Here are some possible strategies for handling the statement q = p,
where p and q are smart pointers:
• Create a new copy of the object pointed by p, and have q point to this copy.

• Ownership transfer: Let both p and q point to the same object, but transfer the responsibility for cleaning up ("ownership") from p to
q. Reference counting: Maintain a count of the smart pointers that point to the same object, and delete the object when this count
becomes zero. So the statement q = p causes the count of the object pointed by p to increase by one.
• Reference linking: The same as reference counting, only instead of a count, maintain a circular doubly linked list of all smart pointers
that point to the same object.
• Copy on write: Smart pointers can be used to make more efficient use of available memory and to shorten allocation and deallocation
time. A common strategy for using memory more efficiently is copy on write (COW). This means that the same object is shared by
many COW pointers as long as it is only read and not modified. When some part of the program tries to modify the object ("write"),
the COW pointer creates a new copy of the object and modifies this copy instead of the original object.

Types of Smart Pointers


There are several types of smart pointers. Some work with reference counting, others by assigning ownership of the object to a single pointer.
We find the following smart pointer implementations:
shared_ptr<T> pointer to T" using a reference count to determine when the object is no longer needed.
a pointer automatically deleted when it goes out of scope. No assignment possible, but no performance
scoped_ptr<T>
penalties compared to "raw" pointers
another reference counting pointer. It provides better performance than shared_ptr, but requires the type T
intrusive_ptr<T>
to provide its own reference counting mechanism.
weak_ptr<T> a weak pointer, working in conjunction with shared_ptr to avoid circular references
shared_array<T> like shared_ptr, but access syntax is for an Array of T
scoped_array<T> like scoped_ptr, but access syntax is for an Array of T

Smart Pointers 3
Smart Pointer Implementation:
In C++, smart pointers are implemented as a template class that behaves like a traditional (normal) pointers, (e.g.: dereferencing, assignment)
while providing additional memory management algorithms.
It does so by means of operator overloading.

Stack Unwinding
We know that in C++ throw, try, and catch work together to enable exception handling.
When an exception is thrown, the runtime mechanism first searches for an appropriate handler (a catch statement) for it in the current scope i.e.
first the program looks to see if the exception can be handled immediately (which means it was thrown inside a try block). If such a handler does
not exist, the current scope is exited and the function that is higher in the calling chain is entered into scope i.e. if no handler found in the
function, it immediately terminates the current function and checks to see if the caller will handle the exception. If not, it terminates the caller
and checks the caller’s caller. This process is iterative; it continues until an appropriate handler has been found i.e. each function is terminated in
sequence until a handler for the exception is found, or until main() terminates. An exception is considered to be handled upon its entry to a
handler. At this point, all the local objects that were constructed on the path from a try block to a throw-expression have been destroyed. In
other words, the stack has been unwound. If no exception handler is found even in the main() the application terminates.
During stack unwinding, all the local objects in all those stack frames are destructed.

Stack Unwinding Example


Here’s another example showing stack unwinding in practice, using a larger stack. Although this program is long, it’s pretty simple: main() calls
First(), First() calls Second(), Second() calls Third(), Third() calls Last(), and Last() throws an exception.

01 #include <iostream>
02 using namespace std;
03
04 void Last() // called by Third()
05 {
06 cout << "Start Last" << endl;
07 cout << "Last throwing int exception" << endl;
08 throw -1;
09 cout << "End Last" << endl;
10
11 }
12
13 void Third() // called by Second()
14 {
15 cout << "Start Third" << endl;
16 Last();
17 cout << "End Third" << endl;
18 }
19
20 void Second() // called by First()
21 {
22 cout << "Start Second" << endl;
23 try
24 {
25 Third();
26 }
27 catch(double)
28 {
29 cerr << "Second caught double exception" << endl;
30 }
31 cout << "End Second" << endl;
32 }
33
34 void First() // called by main()
35 {
36 cout << "Start First" << endl;
37 try
38 {
39 Second();
40 }
41 catch (int)
42 {
43 cerr << "First caught int exception" << endl;
44 }
45 catch (double)
46 {
47 cerr << "First caught double exception" << endl;
48 }
49 cout << "End First" << endl;
50 }
51
52 int main()
53 {
54 cout << "Start main" << endl;
55 try
56 {
57 First();
58 }
59 catch (int)
60 {

Smart Pointers 4
61 cerr << "main caught int exception" << endl;
62 }
63 cout << "End main" << endl;
64
65 return 0;
66 }

Take a look at this program in more detail, and see if you can figure out what gets printed and what doesn’t when it is run. The answer follows:

Start main
Start First
Start Second
Start Third
Start Last
Last throwing int exception
First caught int exception
End First
End main

Let’s examine what happens in this case. The printing of all the start statements is straightforward and doesn’t warrant further explanation.
Last() prints “Last throwing int exception” and then throws an int exception. This is where things start to get interesting.

Because Last() doesn’t handle the exception itself, the stack begins to unwind. Last() terminates immediately and control returns to the caller,
which is Third().

Third() doesn’t handle any exceptions either, so it terminates immediately and control returns to Second().

Second() has a try block, and the call to Third() is within it, so the program attempts to match the exception with an appropriate catch block.
However, there are no handlers for exceptions of type int here, so Second() terminates immediately and control returns to First().

First() also has a try block, and the call to Second() is within it, so the program looks to see if there is a catch handler for int exceptions. There
is! Consequently, First() handles the exception, and prints “First caught int exception”.

Because the exception has now been handled, control continues normally at the end of the catch block within First(). This means First() prints
“End First” and then terminates normally.

Control returns to main(). Although main() has an exception handler for int, our exception has already been handled by First(), so the catch
block within main() does not get executed. main() simply prints “End main” and then terminates normally.

One more better example is as follows:

If an exception is thrown during construction of an object consisting of subobjects or array elements, destructors are only called for those
subobjects or array elements successfully constructed before the exception was thrown. A destructor for a local static object will only be called if
the object was successfully constructed.
If during stack unwinding a destructor throws an exception and that exception is not handled, the terminate() function is called. The following
example demonstrates this:
#include <iostream>
using namespace std;

struct E
{
const char* message;
E(const char* arg) : message(arg) { }
};

void my_terminate()
{
cout << "Call to my_terminate" << endl;
};

struct A
{
A() { cout << "In constructor of A" << endl; }
~A()
{
cout << "In destructor of A" << endl;
throw E("Exception thrown in ~A()");
}
};

struct B
{
B() { cout << "In constructor of B" << endl; }
~B() { cout << "In destructor of B" << endl; }
};

int main()
{
set_terminate(my_terminate);

try
{
cout << "In try block" << endl;
A a;
B b;
throw("Exception thrown in try block of main()");
}

Smart Pointers 5
catch (const char* e)
{
cout << "Exception: " << e << endl;
}
catch (...)
{
cout << "Some exception caught in main()" << endl;
}

cout << "Resume execution of main()" << endl;


}
The following is the output of the above example:
In try block
In constructor of A
In constructor of B
In destructor of B
In destructor of A
Call to my_terminate
In the try block, two automatic objects are created: a and b. The try block throws an exception of type const char*. The handler catch (const
char* e) catches this exception. The C++ run time unwinds the stack, calling the destructors for a and b in reverse order of their construction.
The destructor for a throws an exception. Since there is no handler in the program that can handle this exception, the C++ run time calls
terminate(). (The function terminate() calls the function specified as the argument to set_terminate(). In this example, terminate() has been
specified to call my_terminate().)

Smart Pointers 6

You might also like