Wednesday, October 20, 2004

Smart Container

When storing data we are encouraged to play nicely and use the STL, however, there are a number of areas where the STL container library frankly does not help a great deal. One of these is storing pointers. It has given us a basic smart pointer type that cannot be stored in containers and containers that do not have specific handling for pointers.
Storing pointers directly can lead to ambiguities of ownership that can cause anything from resource leaks to access violations.
Will the Developer remember to delete them?
Should the lifetime of a pointer be handled by the program and if so how?

Much store is made of guarantees. Guarantees are difficult to achieve with pointers. Either the pointer is hidden totally, access and usability are sacrificed, or a limited guarantee is achieved. A number of options are available for resource handling and a number of smart pointer types available to express them.
Wouldn’t it be simpler, on those occasions where it is appropriate, to have a container that calls delete on all of its’ pointers as it goes out of scope a "smart container". In that case the primary guarantee will be "This class WILL clean up your resources when it is destroyed, no matter what." The maximum extension of flexibility that can be offered to the user of the contained class can only be "you can manage its’ creation and use within this scope". This is a stronger guarantee than that of reference counting and other similar methodologies.
So when would a class like this be useful?
When strictly limited resources are being handled.
Examples would be; Persistent Object Brokers, Sessions, Mutexes, Device contexts etc.

So what do we want from this smart container?
It needs to be usable in the same way as the STL and similarly generic.
It needs to be extensible to allow it to be used specifically.
It needs to be flexible and provide a number of default options to make it quick and easy to use.

After much experimentation from trying a container of hidden generic smart pointers (yes, seriously I did...) to private inheritance, I have decided on a policy based template class. Policy based development allows a flexible approach to class design. Policies can be "plugged-in" by the user of the class to provide a number of design options which offer flexibility without having to program it each and every time. Added to which they’re dead cool.

Here is an example. Exception handling etc. has been omitted for clarity and through laziness.


#pragma once

#include "stdafx.h"
#include <vector>
#include <list>
#include <iostream>
#include <algorithm>

using namespace std;


struct DeleteObj
{
template<class T>
void operator()( T& p )
{
if(p)
delete p;
else
ASSERT(p);
}
};

struct DeleteAndNullObj
{
template<class T>
void operator()( T& p )
{
delete p;
p =0;
}
};

struct DeleteAndSetBytePattern
{
template<class T>
void operator()( T& p )
{
delete p;
p =reinterpret_cast<T>(0xDEADBEEF);
}
};

struct ContainerDelete
{
template<class T>
void Delete( T& container )
{
std::for_each( container.begin(),
container.end(),
DeleteObj() );
}
};

struct ContainerDeleteAndNull
{
template<class T>
void Delete( T& container )
{
std::for_each( container.begin(),
container.end(),
DeleteAndNullObj() );
}
};

struct ContainerDeleteAndSetBytePattern
{
template<class T>
void Delete( T& container )
{
std::for_each( container.begin(),
container.end(),
DeleteAndSetBytePattern() );
}
};


template
<
class Elem
,class Container = vector< Elem >
,bool bDeleteAlways = true
,class DeletionPolicy = ContainerDelete
>

class SmartContainer : public DeletionPolicy
{
public:
// typedefs expose the member container's types as our own

typedef typename Container::iterator iterator;
typedef typename Container::const_iterator const_iterator;

SmartContainer(){};
SmartContainer(const int i_sz) : cont_(i_sz) {};

~SmartContainer()
{
if(bDeleteAlways)
{
Delete( cont_ );
}
}

// exposure of member container functionality
void push_back(Elem val)
{
cont_.push_back(val);
}

size_t size()
{
return cont_.size();
}

iterator begin()
{
return cont_.begin();
}

iterator end()
{
return cont_.end();
}


// extending its interface to make it more useful
void DeleteObjects()
{
if( ! bDeleteAlways)
{
Delete(cont_);
}
}

private:
Container cont_;
};

No comments: