Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 15

Advantages of Templates [C++]

You can use templates to:

 Create a typesafe collection class (for example, a stack) that can operate on data of any type.
 Add extra type checking for functions that would otherwise take void pointers.
 Encapsulate groups of operator overrides to modify type behavior (such as smart pointers).

Most of these uses can be implemented without templates; however, templates offer several advantages:

 Templates are easier to write. You create only one generic version of your class or function instead of
manually creating specializations.
 Templates can be easier to understand, since they can provide a straightforward way of abstracting
type information.
 Templates are typesafe. Because the types that templates act upon are known at compile time, the
compiler can perform type checking before errors occur.

http://msdn.microsoft.com/en-us/library/z3f89ch8(VS.80).aspx

Type checking is the processes of identifying errors in a program based on explicitly or implicitly stated
type information

 Weak typing
 Type errors can lead to erroneous calculations
 Strong typing
 Type errors cannot cause erroneous calculations
 The type check is done at compile time or run time
 Static typing
 The types of all expressions are determined before the program is executed
 The type check is typically carried out in an early phase of the compilation
 Comes in two flavors: explicit type decoration and implicit type inference
 Static typing implies strong typing

C++ Void Pointer and Null Pointer

In this C++ tutorial, you will learn about two interesting types of pointers; void pointers and Null
Pointer. These pointers will be discussed in conjunction with syntax, usage and example.
Pointer to Void

General Syntax:

void* pointer_variable;

Void is used as a keyword.

Referring back to pointer definitions and usage, it is known that the data type the pointer variable
defines is the same as the data type the pointer points to. The address placed in a pointer must
have the same type as the pointer.

For example:

int i;
float f;
int* exf;
float* test;
then
exf=&i;

Is correct because the address of integer variable is stored in an integer pointer.

If a user writes the statement:

exf=&f;
Then this statement produces an error. The address of the float variable is stored in an integer
pointer that is incorrect.

Similarly, if the programmer tries to place the address of an integer variable to a float pointer,
such as:

test=&i;

The above statement will also show an error.

The Pointer to Void is a special type of pointer that the programmer can use to point to any data
type.

Using the above example, the programmer declares pointer to void in this manner:

void* sample;

Using the above example’s definition and assigning the pointer to void to the address of an
integer variable is perfectly correct.

sample=&i;

Using the above example to define the pointer to void and assign the pointer to void to the
address of a float variable as below is also perfectly correct.

sample=&f;
Pointer to void, or a void pointer, is a special type of pointer that has a great facility of pointing
to any data type. There are limitations in the usage of void pointers that are explained below.

The concept of dereferencing using the operator * has been explained in an earlier section of this
tutorial. The programmer must note that void pointers cannot be de-referenced in the same
manner. Direct dereferencing of void pointer is not permitted. The programmer must change the
pointer to void as any other pointer type that points to valid data types such as, int, char, float and
then dereference it. This conversion of pointer to some other valid data type is achieved by using
the concept of type-casting (refer to type-casting section of this tutorial).

NULL Pointer:

The concept of NULL pointer is different from the above concept of void pointer. NULL pointer
is a type of pointer of any data type and generally takes a value as zero. This is, however, not
mandatory. This denotes that NULL pointer does not point to any valid memory address.

For example:

int* exforsys;
exforsys=0;

The above statement denotes exforsys as an integer pointer type that does not point to a valid
memory address. This shows that exforsys has a NULL pointer value.

The difference between void pointers and NULL pointers:

A Void pointer is a special type of pointer of void and denotes that it can point to any data type.
NULL pointers can take any pointer type, but do not point to any valid reference or memory
address. It is important to note that a NULL pointer is different from a pointer that is not
initialized.
For example, if a programmer uses the program below:

#include <iostream.h>
int *exforsys=NULL;
void main()
{
  *exforsys=100;
}

The output of the above program is

NULL POINTER ASSIGNMENT

The above program will result in a runtime error. This means that the pointer variable exforsys is
not assigned any valid address and, therefore, attempting to access the address 0 gives the above
error message.

What are they?


Smart pointers are objects that look and feel like pointers, but are smarter. What does this mean?

To look and feel like pointers, smart pointers need to have the same interface that pointers do:
they need to support pointer operations like dereferencing (operator *) and indirection (operator
->). An object that looks and feels like something else is called a proxy object, or just proxy. The
proxy pattern and its many uses are described in the books Design Patterns and Pattern Oriented
Software Architecture.

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...

The simplest example of a smart pointer is auto_ptr, which is included in the standard C++
library. You can find it in the header <memory>, or take a look at Scott Meyers' auto_ptr
implementation. Here is part of auto_ptr's implementation, to illustrate what it does:
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;}
// ...
};
As you can see, auto_ptr is a simple wrapper around a regular pointer. It forwards all meaningful
operations to this pointer (dereferencing and indirection). Its smartness in the destructor: the destructor
takes care of deleting the pointer.

For the user of auto_ptr, this means that instead of writing:

void foo()
{
MyClass* p(new MyClass);
p->DoSomething();
delete p;
}
You can write:

void foo()
{
auto_ptr<MyClass> p(new MyClass);
p->DoSomething();
}
And trust p to cleanup after itself.

What does this buy you? See the next section.

Why would I use them?


Obviously, different smart pointers offer different reasons for use. Here are some common reasons for
using smart pointers in C++.

Why: Less bugs


Automatic cleanup. As the code above illustrates, using smart pointers that clean after themselves can
save a few lines of code. The importance here is not so much in the keystrokes saved, but in reducing
the probability for bugs: you don't need to remember to free the pointer, and so there is no chance you
will forget about it.

Automatic initialization. Another nice thing is that you don't need to initialize the auto_ptr to
NULL, since the default constructor does that for you. This is one less thing for the programmer
to forget.

Dangling pointers. A common pitfall of regular pointers is the dangling pointer: 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. This strategy is
implemented in copied_ptr.h.
 Ownership transfer: Let both p and q point to the same object, but transfer the responsibility
for cleaning up ("ownership") from p to q. This strategy is implemented in owned_ptr.h.
 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. This strategy is implemented in counted_ptr.h. Scott
Meyers offers another reference counting implementation in his book More Effective C++.
 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. This strategy is
implemented in linked_ptr.h.
 Copy on write: Use reference counting or linking as long as the pointed object is not modified.
When it is about to be modified, copy it and modify the copy. This strategy is implemented in
cow_ptr.h.

All these techniques help in the battle against dangling pointers. Each has each own benefits and
liabilities. The Which section of this article discusses the suitability of different smart pointers for various
situations.

Why: Exception Safety


Let's take another look at this simple example:

void foo()
{
MyClass* p(new MyClass);
p->DoSomething();
delete p;
}
What happens if DoSomething() throws an exception? All the lines after it will not get executed and p
will never get deleted! If we're lucky, this leads only to memory leaks. However, MyClass may free some
other resources in its destructor (file handles, threads, transactions, COM references, mutexes) and so
not calling it my cause severe resource locks.

If we use a smart pointer, however, p will be cleaned up whenever it gets out of scope, whether it
was during the normal path of execution or during the stack unwinding caused by throwing an
exception.

But isn't it possible to write exception safe code with regular pointers? Sure, but it is so painful
that I doubt anyone actually does this when there is an alternative. Here is what you would do in
this simple case:

void foo()
{
MyClass* p;
try {
p = new MyClass;
p->DoSomething();
delete p;
}
catch (...) {
delete p;
throw;
}
}
Now imagine what would happen if we had some if's and for's in there...

Why: Garbage collection


Since C++ does not provide automatic garbage collection like some other languages, smart pointers can
be used for that purpose. The simplest garbage collection scheme is reference counting or reference
linking, but it is quite possible to implement more sophisticated garbage collection schemes with smart
pointers. For more information see the garbage collection FAQ.

Why: Efficiency
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. The standard string
class is commonly implemented using COW semantics (see the <string> header).

string s("Hello");

string t = s; // t and s point to the same buffer of characters

t += " there!"; // a new buffer is allocated for t before


// appending " there!", so s is unchanged.
Optimized allocation schemes are possible when you can make some assumptions about the
objects to be allocated or the operating environment. For example, you may know that all the
objects will have the same size, or that they will all live in a single thread. Although it is possible
to implement optimized allocation schemes using class-specific new and delete operators, smart
pointers give you the freedom to choose whether to use the optimized scheme for each object,
instead of having the scheme set for all objects of a class. It is therefore possible to match the
allocation scheme to different operating environments and applications, without modifying the
code for the entire class.

Why: STL containers


The C++ standard library includes a set of containers and algorithms known as the standard template
library (STL). STL is designed to be generic (can be used with any kind of object) and efficient (does not
incur time overhead compared to alternatives). To achieve these two design goals, STL containers store
their objects by value. This means that if you have an STL container that stores objects of class Base, it
cannot store of objects of classes derived from Base.

class Base { /*...*/ };


class Derived : public Base { /*...*/ };

Base b;
Derived d;
vector<Base> v;

v.push_back(b); // OK
v.push_back(d); // error
What can you do if you need a collection of objects from different classes? The simplest solution is to
have a collection of pointers:

vector<Base*> v;

v.push_back(new Base); // OK
v.push_back(new Derived); // OK too

// cleanup:
for (vector<Base*>::iterator i = v.begin(); i != v.end(); ++i)
delete *i;
The problem with this solution is that after you're done with the container, you need to manually
cleanup the objects stored in it. This is both error prone and not exception safe.

Smart pointers are a possible solution, as illustrated below. (An alternative solution is a smart
container, like the one implemented in pointainer.h.)

vector< linked_ptr<Base> > v;


v.push_back(new Base); // OK
v.push_back(new Derived); // OK too

// cleanup is automatic
Since the smart pointer automatically cleans up after itself, there is no need to manually delete the
pointed objects.
Note: STL containers may copy and delete their elements behind the scenes (for example, when
they resize themselves). Therefore, all copies of an element must be equivalent, or the wrong
copy may be the one to survive all this copying and deleting. This means that some smart
pointers cannot be used within STL containers, specifically the standard auto_ptr and any
ownership-transferring pointer. For more info about this issue, see C++ Guru of the Week #25.

Which one should I use?


Are you confused enough? Well, this summary should help.

Which: Local variables


The standard auto_ptr is the simplest smart pointer, and it is also, well, standard. If there are no special
requirements, you should use it. For local variables, it is usually the right choice.

Which: Class members


Although you can use auto_ptr as a class member (and save yourself the trouble of freeing objects in the
destructor), copying one object to another will nullify the pointer, as illustrated Below.

class MyClass
{
auto_ptr<int> p;
// ...
};

MyClass x;
// do some meaningful things with x
MyClass y = x; // x.p now has a NULL pointer
Using a copied pointer instead of auto_ptr solves this problem: the copied object (y) gets a new copy of
the member.

Note that using a reference counted or reference linked pointer means that if y changes the
member, this change will also affect x! Therefore, if you want to save memory, you should use a
COW pointer and not a simple reference counted/linked pointer.

Which: STL containers


As explained above, using garbage-collected pointers with STL containers lets you store objects from
different classes in the same container.

It is important to consider the characteristics of the specific garbage collection scheme used.
Specifically, reference counting/linking can leak in the case of circular references (i.e., when the
pointed object itself contains a counted pointer, which points to an object that contains the
original counted pointer). Its advantage over other schemes is that it is both simple to implement
and deterministic. The deterministic behavior may be important in some real time systems,
where you cannot allow the system to suddenly wait while the garbage collector performs its
housekeeping duties.

Generally speaking, there are two ways to implement reference counting: intrusive and non-
intrusive. Intrusive means that the pointed object itself contains the count. Therefore, you cannot
use intrusive reference counting with 3-rd party classes that do not already have this feature. You
can, however, derive a new class from the 3-rd party class and add the count to it. Non-intrusive
reference counting requires an allocation of a count for each counted object. The counted_ptr.h is
an example of non-intrusive reference counting.

Reference linking does not require any


changes to be made to the pointed
objects, nor does it require any
additional allocations. A reference linked
pointer takes a little more space than a
reference counted pointer - just enough
to store one or two more pointers.

Both reference counting and reference linking require using locks if the pointers are used by
more than one thread of execution.

Which: Explicit ownership transfer


Sometimes, you want to receive a pointer as a function argument, but keep the ownership of this
pointer (i.e. the control over its lifetime) to yourself. One way to do this is to use consistent naming-
conventions for such cases. Taligent's Guide to Designing Programs recommends using "adopt" to mark
that a function adopts ownership of a pointer.

Using an owned pointer as the function argument is an explicit statement that the function is
taking ownership of the pointer.

Which: Big objects


If you have objects that take a lot of space, you can save some of this space by using COW pointers. This
way, an object will be copied only when necessary, and shared otherwise. The sharing is implemented
using some garbage collection scheme, like reference counting or linking.
Which: Summary
For this: Use that:

Local variables auto_ptr

Class members Copied pointer

STL Containers Garbage collected pointer (e.g. reference counting/linking)

Explicit ownership transfer Owned pointer

Big objects Copy on write

Conclusion
Smart pointers are useful tools for writing safe and efficient code in C++. Like any tool, they should be
used with appropriate care, thought and knowledge. For a comprehensive and in depth analysis of the
issues concerning smart pointers, I recommend reading Andrei Alexandrescu's chapter about smart
pointers in his book Modern C++ Design.

Feel free to use my own smart pointers in your code.


The Boost C++ libraries include some smart pointers, which are more rigorously tested and
actively maintained. Do try them first, if they are appropriate for your needs.
What is a "smart pointer"?
Smart pointer (as opposed to a "dumb" or "raw" pointer) is a C++ structure that behaves almost
identically to a common C pointer but it also includes some other capabilities, e.g. throws an
exception when it's NULL and someone tries to dereference it, or it destroys its contents
automatically when it goes out of scope.

What are the basic implementation schemes for smart pointers?


In order to increase a pointer's IQ, you need to maintain somewhere some extra data about the
object that it points to. There are only 3 places that this data may be stored:
a) in the object itself
b) in the pointer
c) in some central depository
In the first case, the smart pointers would need some kind of support from the objects
themselves, therefore the scheme is usually referred to as intrusive scheme. The other two cases
are the non-intrusive schemes.

What are the pros and cons of intrusive smart pointers?


Intrusive smart pointers usually work only with classes that inherit from some central "mother"
class, which provides the support the pointers need. Therefore they cannot work with intrinsic
data types (int, float, char, etc) neither with legacy or library classes. On the other hand, they can
be very safe, and can be mixed easily with raw pointers. So you can use smart pointers for your
own classes, and raw pointers for everything else. Or, you can wrap legacy and library classes to
add smart pointer support (simply create a wrapper class which inherits from both the legacy
class and the smart pointer "mother" class).

What are the pros and cons of non-intrusive smart pointers?


Non-intrusive smart pointers can work with any type of class (yours, library, or legacy classes)
and also with intrinsic data types. However, they do not mix well with raw pointers, and
therefore it is impossible to guarantee safety: the moment someone goes from a smart pointer to
a raw pointer back to a smart pointer, you got a problem. The raw pointers acts as a firewall
which prevents the smart pointer data to be transferred, so you end up with two different smart
pointers having two different versions of the data. Disaster may strike at any moment! Even by
storing the data in a central depository doesn't really solve the problem: if a raw pointer deletes
the object, then the central depository is not updated. So it becomes "corrupted"... Once more,
fire and destruction is imminent...

Does this mean intrusive schemes are better than non-intrusive ones?
Yes. Non-intrusive schemes are not worth the risk. Intrusive schemes may not be as convenient,
however, they are very safe. And the whole point of using smart pointers is improved safety,
no?

Enough with the theory. Can you give me a smart pointer implementation to try?
Sure, a small header file is all you need! Click the following link to download an implementation
of an "intrusive" smart pointer scheme:
Download smart pointers code - click here:   Smart pointer implementation

The above link is the header file Ptr.h that contains a complete smart pointer implementation.
Usage notes:
* include this header file into your code
* declare your smart pointers as  Ptr<MyClass>  instead of  MyClass*
* in order for a class to work with these smart pointers, it needs to inherit from SmartObject
and you need to define the GetClassName() function.

That's it, you're ready to go! Now the following pointer-related errors will throw a nice exception
that you can catch and handle instead of crashing your program:
-  using an uninitialized pointer
-  using a NULL pointer
-  using a deleted pointer
-  having a memory leak

With a trivial 1-line change you can also have it delete memory automatically similar to a
reference counting scheme, instead of reporting memory leaks. However this was not my goal,
so the default behavior is that you should delete all heap-allocated memory, and when you forget
it, you get an informative memory leak message, exactly at the code statement where the
memory leak occurs (instead of a generic "memory leak" at the end of the program). Why did I
implement it like this? Because I wanted to preserve the semantics of "raw pointers" so that the
two are interchangeable (see Bonus 2 below). However, you can easily change this behavior -
contact me if you have trouble figuring it out.

In general, you can use these smart pointers exactly as you did with the raw pointers, except
performing pointer arithmetic which is disallowed (pointer arithmetic is very unsafe anyway, so
it is discouraged). You can mix freely these smart pointers with raw pointers,  e.g. smartPtr =
rawPtr;  rawPtr = smartPtr; ...etc...

The primary goal of this smart pointer implementation was to increase programming safety.
However, it also offers 2 bonuses:

Bonus 1
In the Ptr.h file you can also find the AutoPtr<MyClass> construct, which is basically a smart
pointer that deletes automatically its contents when it goes out of scope. Extremely useful when
allocating temporary memory in functions with multiple exit points, or in guarding against
memory leaks when an exception is thrown.

Bonus 2
You can very easily measure the difference in performance between "raw" pointers and the
smart pointers defined here. How? By simply commenting out the definition of
_SMART_PTR_ in the beginning of the Ptr.h file! This converts the Ptr template from a smart
pointer into an almost raw pointer (at least when function inlining is turned on, e.g. in Release
mode of VC++).
 

You might also like