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:
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
{
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:
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:
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:
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:
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:
GEN_RET_NONE() GEN_RET_TYPE(type) GEN_RET_DEPEND_TYPE()
GEN_PARAMS_NONE() GEN_PARAMS(seq) GEN_TYPE(type) GEN_DEPEND_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:
GEN_NAME(name) GEN_METHOD_SIG(sig) GEN_TRAILING_CONST() GEN_THROW_NONE() GEN_THROW_OF(exc_type)
For our audio_filter
sample, let's create generic_ptr
by generator.
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:
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
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.