Smart Pointers
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.
• 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.
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.
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.
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;
}
Smart Pointers 6