Click here to Skip to main content
15,893,722 members
Articles / Programming Languages / C++

generic_ptr and Its Generator

Rate me:
Please Sign up or sign in to vote.
4.29/5 (4 votes)
9 Mar 2010CPOL5 min read 17.3K   9  
Generic object wrapper for delayed type selection

Introduction

Have you ever tried to use a generic object while type selection is made on run time. If yes, you know what the problem here is. If no, check the sample code below:

C++
 // sample_t can be short, float or double
template<typename sample_t>
class audio_filter
{
public:
	const short* process(short* _samples_p, int _sample_count)
	{
		...
	}
	...
};

enum precision{low_precision,normal_precision,high_precision};

class audio_manager
{
	// we can't declare audio filter like this
	//audio_filter<sample_t>* m_audio_filter_p;
public:	
	void initialize(precision _precision,...)
	{
		if (_precision == low_precision)
		{
			m_audio_filter_p = new audio_filter<short>());
		}
		else if (_precision == normal_precision)
		{
			m_audio_filter_p = new audio_filter<float>());
		}
		else if (_precision == high_precision)
		{
			m_audio_filter_p =new audio_filter<double>());
		}
		...	
	}

	void process(short* _samples_p, int _sample_count)
	{
		...
			m_audio_filter_p->process(_samples_p,_sample_count);
		...
	}
	...
};

As you see, you can't declare audio_filter<sample_t> as this is. You need some kind of wrapper class that holds all possible audio_filter<sample_t>.
You can use boost::variant or maybe boost::any for a limited number of type selections. However, this approach has drawbacks. You have to add selection or visitor codes (please refer to boost::variant or boost::any documentation) to your host class and for all new added type options for the generic object. And also what happens if the type options are heavy. For example:

C++
template<typename video_encoder_t, typename audio_encoder_t, typename muxer_t>
class compression 
{
};

Lets say, for video_encoder_t we have 3 options as none_t, mpg, and h264; for audio_encoder_t we have 4 options as none_t, pcm, aac and mpg; for muxer_t we have 4 options as none_t, mpg, mp4 and mxf. For all options, we have to define 48 different compression classes. Try to imagine the definition of boost::variant or type selection code for boost::any.

Solution

We need a wrapper class such that the host class is not aware there is a wrapper at all, don't need to add extra code to use that wrapper and don't need to add extra code for new type options for generic object.

The whole idea for this wrapper has come from the functor implementation of LOKI.
We need to define an interface that declares all the functions of the generic class we want to use. Then we need a generic handler class that implements this interface and forwards the function calls to the generic object that it holds. The wrapper class holds this handler via defined interface pointer.

I think the code will tell everything:

C++
class i_generic
{
public:
  virtual ~i_generic(){}

  virtual bool process(byte_t * _buffer_p, int _length) = 0;
};

template<typename T>
class generic_handler 
: public i_generic
{
  typedef boost::shared_ptr<T> obj_sp_t;
public:
  generic_handler(obj_sp_t _obj_sp) : m_obj_sp (_obj_sp){}

  bool process(byte_t * _buffer_p, int _length)
  {
    return m_obj_sp->process(_buffer_p,length);
  }
private:
  boost::shared_ptr<T> m_obj_sp;
};

class generic_ptr
{
  typedef boost::shared_ptr<i_generic> i_generic_sp_t;
public:
  generic_ptr(){}
  
  template<typename T>
  generic_ptr(boost::shared_ptr<T> _obj_sp)
  {
    m_handler_sp.reset(new generic_handler<T>(_obj_sp));
  }
  template<typename T>
  void reset(boost::shared_ptr<T> _obj_sp)
  {
    m_handler_sp.reset(new generic_handler<T>(_obj_sp));
  }
  i_generic* operator->(){return m_handler_sp.get();}
private:
  i_generic_sp_t m_handler_sp;
};

Now we can use this wrapper as we wanted:

C++
class audio_manager
{
	generic_ptr m_audio_filter_p;
public:	
	void initialize(precision _precision,...)
	{
		if (_precision == low_precision)
		{
			m_audio_filter_p = new audio_filter<short>());
		}
		else if (_precision == normal_precision)
		{
			m_audio_filter_p = new audio_filter<float>());
		}
		else if (_precision == high_precision)
		{
			m_audio_filter_p =new audio_filter<double>());
		}
		...	
	}
	
	void process(short* _samples_p, int _sample_count)
	{
		...
		m_audio_filter_p->process(_samples_p,_sample_count);
		...
	}
...
};

Dependant Types

What about dependant return or argument types on generic class? We can't declare dependant types of generic class on our interface. We need something that holds all possible data types and can be converted back to the dependant type on handler class before forwarding the calls. I used boost::any for this purpose.

There is a limitation that you have to create boost::any (implicitly or explicitly) at the call site as the handler’s expected type, otherwise the boost::any_cast (used converting boost::any to dependant type on handler class) complains about that (throws an exception).

Let’s return our audio_filter sample and add functions with dependant types:

C++
template<typename T>
class audio_filter
{
public:
	typedef T sample_t;
public:
	void set_gain(sample_t _gain){...}
	sample_t get_gain()const{...}
...
};

class i_generic
{
public:
  virtual ~i_generic(){}
public:
  virtual void set_gain(boost::any) = 0;
  virtual boost::any get_gain() = 0;
};

template<typename T>
class generic_handler 
: public i_generic
{
  typedef boost::shared_ptr<T> obj_sp_t;
public:
  void set_gain(boost::any _gain)
  {
    m_obj_sp->set_gain(boost::any_cast<typename T::sample_t>(_gain));
  };
  boost::any get_gain()const
  {
    return m_obj_sp->get_gain();
  }
private:
  boost::shared_ptr<T> m_obj_sp;
};

generic_ptr Generator

What are the drawbacks of this approach? Well, first you have to define two extra classes, the interface and the handler. And also, you have to update the interface and the handler classes, if you want to support new functions of the generic class.

In order to simplify the code generation, I tried to write a generic_ptr generator. I used boost::preprocessor library and macro sequences.

I tried to keep the macros as simple as possible, however I faced some problems and need to define extra macros that I didn't intend to at the beginning.

My first struggle was “return” keyword. On function generation, I have to know whether the functions must return anything or not. Therefore I had to define an extra macro. And also, I need extra macros for dependant parameter and dependant return type detection.

The return and function parameter type macros are:

C++
GEN_RET_NONE()  		//defines void
GEN_RET_TYPE(type) 	//define return type
GEN_RET_DEPEND_TYPE() 	//defines dependant return type(does not get any parameter)

GEN_PARAMS_NONE() 		//defines no parameter (used for void).
GEN_PARAMS(seq) 		//all function parameters must be defined inside this macro
GEN_TYPE(type) 		//define parameter type
GEN_DEPEND_TYPE 		//defines dependant parameter type

There is a limitation for the dependant types. The dependant types must be defined (or redefined) in generic class (see above sample_t typedef in the audio_filter). The generic class type of the generated handler class will be T, therefore dependant types will be defined as “typename T::type

Because I used macro sequences, I need some macros to create sequences as:

C++
GEN_NAME(name) 		// (name)
GEN_METHOD_SIG(sig) 	// (sig)
GEN_TRAILING_CONST() 	// (const)
GEN_THROW_NONE() 		// (throw())
GEN_THROW_OF(exc_type) 	// (throw(exc_type))

For our audio_filter sample, let's create generic_ptr by generator.

C++
template<typename T>
class audio_filter
{
public:
	typedef T sample_t;

	void initialize(int _average_sample_count)throw(std::exception){ ...}
	void set_gain(sample_t _gain){...}
	sample_t get_gain()const{...}
	const short* process(short* _samples_p, int _sample_count){...}
	int get_total_samples()const{...}
};

GENERIC_PTR
(
 GEN_NAME(audio_info)
 GEN_METHOD_SIG(GEN_RET_NONE() GEN_NAME(initialize) 
	GEN_PARAMS(GEN_TYPE(int)) GEN_THROW_OF(std::exception))
 GEN_METHOD_SIG(GEN_RET_NONE() GEN_NAME(set_gain) 
	GEN_PARAMS(GEN_DEPEND_TYPE(typename T::sample_t)))
 GEN_METHOD_SIG(GEN_RET_DEPEND_TYPE() GEN_NAME(get_gain) 
	GEN_PARAMS_NONE() GEN_TRAILING_CONST())
 GEN_METHOD_SIG(GEN_RET_TYPE(const short*) GEN_NAME(process) 
	GEN_PARAMS(GEN_TYPE(short*) GEN_TYPE(int)))
 GEN_METHOD_SIG(GEN_RET_TYPE(int) GEN_NAME(get_total_samples) 
	GEN_PARAMS_NONE() GEN_TRAILING_CONST())
)

The generated interface name is i_generic_NAME, the handler class name is generic_NAME_impl and generic_ptr class name is generic_NAME_ptr_t. If the name is “<code><code><code>audio_filter” (as above), GENERIC_PTR macro generates msh::utility::generic_audio_info_ptr_t class (actually typedef of the msh::utility::generic_ptr<i_generic_audio_info,generic_audio_info_impl>)
I know macro definitions seem complicated, however if you want to add a new function, you just have to add a GEN_METHOD_SIG(…) at all.

generic_ptr Implementation

This is the generic_ptr implementation:

C++
namespace msh {namespace utility {

	template<typename interface_t, template <typename> class handler_t>
	class generic_ptr
	{
		typedef boost::shared_ptr<interface_t>interface_sp_t;
	public:
		generic_ptr(){}

		template<typename obj_t>
		generic_ptr(boost::shared_ptr<obj_t> _obj_sp)
		{
			m_obj_sp.reset(new handler_t<obj_t>(_obj_sp));
		}

		template<typename obj_t>
		generic_ptr & operator=( boost::shared_ptr<obj_t> _obj_sp )
		{
			m_obj_sp.reset(new handler_t<obj_t>(_obj_sp));
			return *this;
		}

		template<typename obj_t>
		void reset(boost::shared_ptr<obj_t> _obj_sp)
		{
			m_obj_sp.reset(new handler_t<obj_t>(_obj_sp));
		}

		interface_t* operator->()const{return m_obj_sp.get();}

		void swap( generic_ptr const & r )
		{
			m_obj_sp.swap(r.m_obj_sp);
		}

	private:
		interface_sp_t m_obj_sp;
	};
}}

This is quite simple and a straightforward class (because all work is done on handler class).

generic_ptr is implemented as template in order not to re-write for all generic object types. It takes generic interface and the handler types (as a template parameter). It provides constructor without parameter for delayed type selection for generic object. Later you can initialize internal handler via reset or =operator. For almost all smart pointers, the ->operator is overloaded to return generic interface pointer for seamless function forwarding.

Conclusion

Composition of the generic object with run time type selection can be a tedious work. For limited number of type options, boost::variant or boost::any can be used as a wrapper of the generic object. However, the selection or visitor codes make the host class code complicated. And also these wrappers are sensitive to the types because you have to modify the selection or visitor codes for newly added types.

For this generic_ptr wrapper class (actually 3 separate classes), I was inspired by the functor implementation of the LOKI. With generic_ptr, you don't have to add extra selection or visitor codes to your host class. Although you have to define two extra classes (one for interface, one for handler), with the presented generator, I believe this work is simplified.

The drawbacks of this wrapper are, function call forwarding with virtual functions and the code manipulation for the interface and the handler classes for new functions that you want to use from generic class. I think, the virtual function call dispatching overhead, can be ignored unless you are working on an extreme case that no run time overhead is accepted. And I believe that, the code manipulation can be simplified by the generator that presented.

This is my first time with boost::preprocessor library therefore; I believe that this generator can be written more elegant by experienced users. If you have any propositions, please let me know.

This wrapper proposal is not something new; I know, there are people out there using the same approach with different names. I just want to share this idea with others that are looking for something like this.

Notes

The sample code is tested on:

  • Windows (XP): Visual Studio 2008 with boost_1_40
  • Linux (Pardus): EclipseCDT (gcc v4.3.3) with boost_1_38

History

  • 10/03/2010 First proposal

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Metus Technology
Turkey Turkey
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --