Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

virtual template method / design problem

7 views
Skip to first unread message

Gregory Pakosz

unread,
Jan 28, 2003, 5:20:21 PM1/28/03
to
I have the following design scheme:

the template object "Obj" enables me to hold float or int values

template<typename T>
class Obj
{
public:
Obj() {};
~Obj() {};
const T& GetValue() {return value);
void SetValue(const T& v);

private:
T value;
};

First i had the class ObjIO which enabled me to load/save an Obj to disc

class ObjIO
{
public:
ObjIO() {};
virtual ~ObjIO() {};

template<typename T>
void Load(const std::string& filename, Obj<T>& obj);
template<typename T>
void Save(const std::string& filename, Obj<T>& obj);
};

Since the Load/Save methods were templatized, i could load/save Obj<float>
or Obj<int> with the same ObjIO class.

Then i decided that i needed an ObjIO factory, and 2 ways of storing the Obj
class : text and binary format
So i wanted to derivate 2 classes from ObjIO : TextIO and BinaryIO

If the Obj class was not templatized, i would have made Load/Save methods
pure virtual. ObjIO would have been an abstract class, in fact an interface
returned by the factory. The factory would have instanciate a TextIO or
BinaryIO depending of a parameter passed to a create function. The TextIO or
BinaryIO would have been seen from a client side as a base class reference
or pointer to ObjIO.

At this point, i hope all I have said is clear enough.
Since template virtual methods are not allowed, i had 2 solutions :
- templatize the whole ObjIO,TextIO and BinaryIO
- make 2 separate classes : ObjFloat and ObjInt and overload the Load/Save
methods

I choosed the first solution, but i'm not satisfied. I would like to know if
there is a well known way of doing what i'm trying to do : faking template
virtual method

maybe by encapsulating Obj<T> to a non template type (i just read a
technique about smart function pointers implemented with external
polymorphism)
i'm new to those design schemes, and i'm a bit lost, maybe there is no
obvious solution to my problem

Best regards,
Gregory

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]

Ulrich Eckhardt

unread,
Jan 29, 2003, 6:41:18 AM1/29/03
to
Gregory Pakosz wrote:
[snipped problem]

> Since template virtual methods are not allowed, i had 2 solutions :
> - templatize the whole ObjIO,TextIO and BinaryIO
> - make 2 separate classes : ObjFloat and ObjInt and overload the
> Load/Save
> methods
>
> I choosed the first solution, but i'm not satisfied. I would like to know
> if there is a well known way of doing what i'm trying to do : faking
> template virtual method
>

I can't propose a 'real' solution to your problem, but maybe one that is
close. I assume that your IO will at some point consist of basic things
like integers, strings, spaces, linebreaks (separators for text). How about
defining a set of basic IO-operations as virtual (BinaryIO would simply
ignore a call to its WriteSpace() method while TextIO would write a space)
and then add a template-version to the ObjIO baseclass for writing Obj<> ?

Btw, an almost similar problem is solved by C++ iostreams:
- different representations/output: there it is achieved by facets/locale
which are plugged into the stream for formatting.
- selecting locale and partially facets can be accomplished via a
factory-like interface.
- IO of complex objects is based on simple predefined IO-operations. The
difference here is that the handling of complex objects is external to the
class ( in the << and >> operators, technically almost identical to
auxilary functions) thus not binding the class to a specific type.

I hope I could give you some inspiration,
happy hacking!

Uli

Shannon Barber

unread,
Jan 29, 2003, 1:40:04 PM1/29/03
to
Consider overloading << & >> for text IO, and provide read & write for
binary.

IObj
- define virtual methods

tempalte<typename T>
struct CObj<> : IObj
{
'implement IObj
};

Use it to produce concrete classes:
typedef CObj<float> fobj;
typedef CObj<int> iobj;

Chris Theis

unread,
Jan 29, 2003, 2:11:50 PM1/29/03
to

"Gregory Pakosz" <gpa...@ireste.fr> wrote in message
news:b16s9h$hj8$1...@news-reader10.wanadoo.fr...

> I have the following design scheme:
>
> the template object "Obj" enables me to hold float or int values
>
> template<typename T>
> class Obj
> {
> public:
> Obj() {};
> ~Obj() {};
> const T& GetValue() {return value);
> void SetValue(const T& v);
>
> private:
> T value;
> };
>
> First i had the class ObjIO which enabled me to load/save an Obj to disc
>
> class ObjIO
> {
> public:
> ObjIO() {};
> virtual ~ObjIO() {};
>
> template<typename T>
> void Load(const std::string& filename, Obj<T>& obj);
> template<typename T>
> void Save(const std::string& filename, Obj<T>& obj);
> };
>
[SNIP]

I had a similiar problem which I tackled with some kind of policy based
approach. Note that the following code is not tested with a compiler so
there may be typos and other mistakes. It's just meant as kind of a possible
roadmap for a solution. If anyone has any comments and/or suggestions please
let me know.

First of all I'd introduce a host class:

template< template< class T> class TIOMethod, typename TDataType>
class ObjIO : public TIOMethod<TDataType>
{
public:
ObjIO() {};
virtual ~ObjIO() {};

bool Load( const string& FileName ); // load a whole bunch of
objects using inherited function LoadObject and store them in
// m_DataStorage
bool Save( const string& FileName ); // save a whole bunch of
objects using inherited SaveObject

protected:

vector< Obj<TDataTyp> > m_DataStorage;
};

The TIOMethod template class actually implements the Load & Save functions
for binary or ascii IO. Its base class would look something like:

template<typename T>
class CBaseIO {

public:
virtual bool LoadObject( istream& is, Obj<T>& Obj ) = 0;
virtual bool SaveObject( ostream& os, const Obj<T>& Obj ) = 0;
};

The acutal implementations for ascii (or binary) could look like this:

template< typename T>
class CAsciiIO : public CBaseIO<T> {

public:
bool LoadObject( istream& is, Obj<T>& Obj ); { //TODO: add
implementation

};
bool SaveObject( ostream& os, const Obj<T>& Obj ) { // TODO: add
implementation

};
};

For an integer ascii IO object you'd just instantiate
ObjIO< CAsciiIO, int> AsciiStorage;
or
ObjIO< CBinaryIO, int> BinaryStorage;

Probably the syntax with the nested templates for ObjIO is not yet supported
by your compiler then you'd have to change that a little bit.

Hope that helps
Chris

Gregory Pakosz

unread,
Jan 30, 2003, 9:24:11 AM1/30/03
to
> Probably the syntax with the nested templates for ObjIO is not yet
supported
> by your compiler then you'd have to change that a little bit.
>

Unfortunately i'm working with msvc6 which does not support nested
templates definitions, so i cannot try to implement your solution

Though, as far as i can see, you still have to instanciate one IO class
per
Obj<T> (for each type T you need). So this is the same as my first
solution:
having TexTIO & BinaryIO totally templatized and make them inheritate
from a
base (totally templatized too) ObjIO class, leaving the creation job to
a
factory:

ObjIO<int>* int_text_io = Factory<ObjIO<int>>::CreateInstance("text");
ObjIO<int>* int_binary_io =
Factory<ObjIO<int>::CreateInstance("binary");

template<typename T>
class ObjIO:
{
virtual void public Load(std::istream& inStream, Obj<T>& obj) = 0;
virtual void public Save(std::ostream& outStream, Obj<T>& obj) = 0;
};

In fact what i would like to have is a templatized plugin design scheme:
having TextIO and BinaryIO implementation hidden from a client point of
view, but still being able to handle any T type
Thus, templatized member methods would have been fine.

Unfortunately, the plugin design scheme i adopted need a base IO class
to
have virtual Load/Save methods, overloaded by specific plugins: TextIO,
BinaryIO

What is the real problem ? If i want to hide the TextIO and BinaryIO
implementation to the client code, i have to register them to the
factory
within my API initialization, with the types T i plan to use : int &
float

Ok. I register the plugins for int and float, so i end up with 2
factories
instanciated in the API initialization function:
Factory < ObjIO<int> >
Factory < ObjIO<float> >
each factory can create derived classes from ObjIO :
Factory < ObjIO<int> >::CreateInstance("text");
Factory < ObjIO<float> >::CreateInstance("text");
Factory < ObjIO<int> >::CreateInstance("binary");
Factory < ObjIO<float> >::CreateInstance("binary");

BUT the client code has ObjIO and Factory headers.
So it can define a new ObjIO subclass ClientObjIO, register it for a new
type T , say short

RegisterInFactory< ObjIO<short> , ClientObjIO<short> >
clientStorageFormat("client");

UNFORTUNATELY, there is no way to from a client code point of view, to
use
the TextIO or BinaryIO classes with "short", since they are only
registered
for "int" and "float"

Still searching for THE solution :)
Gregory

Gregory Pakosz

unread,
Jan 30, 2003, 6:58:12 PM1/30/03
to
I tried the solution you gave
Since msvc6 does not support nested template definitions, i changed the
TIOMethod class to a non templatized one, making only member function
Load/Save templatized:

class TextIO
{
protected:
TextIO() {}
virtual ~TextIO() {}
template <typename T>
void LoadObject(std::istream& inStream, Obj<T>& obj);
template <typename T>
void SaveObject(std::ostream& inStream, Obj<T>& obj);
};

So i can instanciate things this way:

InkIO<TextIO> textio;

textio is able to deal with any Obj<T>.

I find this solution more elegant than the previous ones.
However, i still have the pb of publishing the TextIO class, since i
cannot
make an abstract BaseIO class with virtual template<typename T>
Load(...) /
Save(...)

Cheers,
Gregory

Chris Theis

unread,
Jan 31, 2003, 11:27:31 AM1/31/03
to

"Gregory Pakosz" <gpa...@ireste.fr> wrote in message
news:b1c3o1$927$1...@news-reader12.wanadoo.fr...

> I tried the solution you gave
> Since msvc6 does not support nested template definitions, i changed
the
> TIOMethod class to a non templatized one, making only member function
> Load/Save templatized:
>
> class TextIO
> {
> protected:
> TextIO() {}
> virtual ~TextIO() {}
> template <typename T>
> void LoadObject(std::istream& inStream, Obj<T>& obj);
> template <typename T>
> void SaveObject(std::ostream& inStream, Obj<T>& obj);
> };
>
> So i can instanciate things this way:
>
> InkIO<TextIO> textio;
>
> textio is able to deal with any Obj<T>.
>
> I find this solution more elegant than the previous ones.
> However, i still have the pb of publishing the TextIO class, since i
> cannot
> make an abstract BaseIO class with virtual template<typename T>
> Load(...) /
> Save(...)
>
> Cheers,
> Gregory
>

Hi Gergory,

I now see your problem but I'll have to meditate on this a little more
although at the moment I'm not sure whether this problem can be solved
with
that design at all. I'll get back to you if I find a solution ;-)

Chris

Chris Theis

unread,
Jan 31, 2003, 12:30:07 PM1/31/03
to

"Gregory Pakosz" <gpa...@ireste.fr> wrote in message
news:b1c3o1$927$1...@news-reader12.wanadoo.fr...
[SNIP]

> I find this solution more elegant than the previous ones.
> However, i still have the pb of publishing the TextIO class, since i
> cannot
> make an abstract BaseIO class with virtual template<typename T>
> Load(...) /
> Save(...)
>

Hi Gregory,

after some meditation I came up with the following. In principle if I
understood you correctly then you want to avoid to instantiate numerous
BinIO and TextIO classes that are specialized for CObj<int> or CObj<float>
or whatever and furthermore store them in a factory using a base class
pointer. For this I'd propose that you drop the templates for the moment and
put the following virtual functions in your base class:

virtual bool Load( CGenericObj& Obj ) = 0;
virtual bool Save( CGenericObj& Obj ) = 0;

These functions will be actually implemented by the TextIO and the BinIO.
The trick is that we invent some kind of common base class for all possible
types. Our CGenericObj (formerly your Obj<T>) represents a generic wrapper
by making use of a templated constructor. This constructor initializes a
pointer to an internal templated wrapper class which will store the passed
value. For this to work we need to derive our internal templated wrapper
from a non-template base which is used to declare the appropriate member
pointer within our CGenericObj class. To access the stored value we need a
template function that takes care of performing the appropriate cast.
Below you'll find an example implementation which should make things
clearer. If you want to find out more about this generic type holder idiom
then you should check out boost's any.hpp which has a more sophisticated
implementation than this quick hack.

Hope that helps
Chris


class CGenericObj;

template<typename T>
const T& GetGenericObjVal(CGenericObj* pObj)
{
// if the value's type matches the passed template parameter we cast our
// wrapper object to the appropriate templated type and return the wrapped
value.
if(pObj && pObj->GetType() == typeid(T) ) {
return
static_cast<CGenericObj::CWrapper<T>*>(pObj->m_pWrapper)->GetValue();
}

return 0;
}

// our generic type holder object
class CGenericObj
{
protected:

class CBaseWrapper { // non-template base!
public:

virtual const std::type_info& GetType() const = 0;
virtual ~CBaseWrapper() {};

};

// the actual internal templated wrapper
template<typename T>
class CWrapper : public CBaseWrapper { // templated base
public:

CWrapper( const T& Value) : m_Value(Value) {};
const std::type_info& GetType() const {
return typeid(T); // return type of wrapped
object
}

const T& GetValue() const { return m_Value; };

protected:

T m_Value;
};

public:

CGenericObj() : m_pWrapper(0) {};

template<typename TDataType>
CGenericObj( const TDataType& Value ) : m_pWrapper( new
CWrapper<TDataType>(Value)) {};

~CGenericObj()
{
delete m_pWrapper;
};

// set data on an empty object which is a feature needed by the Load
functions
template<typename TDataType>
void SetData( const TDataType& Value) {
if( m_pWrapper)
delete m_pWrapper;

m_pWrapper = new CWrapper<TDataType>(Value);
}

const std::type_info& GetType() const
{
if( m_pWrapper )
return m_pWrapper->GetType();

return typeid(void);
}

CBaseWrapper* m_pWrapper;

protected:
CGenericObj( const CGenericObj& rhs); // prevent copy ctor and
assignment op.
CGenericObj& operator=( CGenericObj& rhs);
};

class CBaseIO {

protected:

CBaseIO() {};
CBaseIO( const CBaseIO& rhs );
CBaseIO& operator=(const CBaseIO& rhs );

public:
virtual ~CBaseIO() {};

virtual bool Load( CGenericObj& Obj ) = 0;
virtual bool Save( CGenericObj& Obj ) = 0;

};

class CTextIO : public CBaseIO {

public:
CTextIO() {};

// sample implementations of Load & Save
bool Load( CGenericObj& Obj )
{
int i = 21;
Obj.SetData( i );

return true;
}

bool Save( CGenericObj& Obj )
{
if( GetGenericObjVal<int>( &Obj ) )
cout << "Save ASCII: Obj = " << GetGenericObjVal<int>( &Obj ) << endl;
else if( GetGenericObjVal<float>( &Obj ) ) {
cout << "Save ASCII: Obj = " << GetGenericObjVal<float>( &Obj ) <<
endl;
}

return true;
}

};

class CBinIO : public CBaseIO {

public:
CBinIO() {};

// sample implementations of Load & Save
bool Load( CGenericObj& Obj )
{
float i = 0.3244;
Obj.SetData( i );

return true;
}


bool Save( CGenericObj& Obj )
{
if( GetGenericObjVal<int>( &Obj ) )
cout << "Save binary: Obj = " << GetGenericObjVal<int>( &Obj ) <<
endl;
else if( GetGenericObjVal<float>( &Obj ) ) {
cout << "Save binary: Obj = " << GetGenericObjVal<float>( &Obj ) <<
endl;
}

return true;
}

};


int main()
{
CBinIO BinIO;
CTextIO TextIO;

int i = 5;
float j = 3.14f;

CGenericObj Obj1( i );
CGenericObj Obj2( j );
CGenericObj Obj3, Obj4;


BinIO.Load( Obj3 ); // loading value = 0.3244
cout << "Load Binary: Obj = " << GetGenericObjVal<float>( &Obj3 ) << endl;

TextIO.Load( Obj4 ); // loading value = 21
cout << "Load ASCII: Obj = " << GetGenericObjVal<int>( &Obj4 ) << endl;

BinIO.Save( Obj1); // storing value = 5
TextIO.Save( Obj2); // storing value = 3.14

return 0;

Andreas Huber

unread,
Feb 2, 2003, 9:25:38 AM2/2/03
to
Gregory,

I haven't had a look at your actual problem, but I think it is worth
noting that virtual member function templates _can_ be simulated with
dynamic_cast. The following is a working implementation of "acyclic
visitor" (stolen from Andrei Alexandrescu's "Modern C++ Design"),
prepended by a (commented-out) implementation of the visitor pattern for
a hypothetical C++ supporting virtual member function templates.

HTH,

Andreas

#include <vector>
#include <iostream>

/*
// All the following commented-out stuff is not legal C++, it only
serves // to show an application of virtual member function templates
(which // are not permitted in C++). class Shape {
public:
template< class Visitor >
virtual Accept( Visitor & vtr ) = 0;
};

class Rectangle : public Shape
{
public:
template< class Visitor >
virtual Accept( Visitor & vtr )
{
vtr->Visit( *this );
}
};

class Circle : public Shape
{
public:
template< class Visitor >
virtual Accept( Visitor & vtr )
{
vtr->Visit( *this );
}
};


class MyDrawingVisitor
{
public:
void Visit( Rectangle & ) {}
void Visit( Circle & ) {}
};
*/

// The following is real C++ and was tested with VC++ 7.1
class BaseVisitor
{
public:
virtual ~BaseVisitor() {}
};

template< class VisitedClass >
class Visitor
{
public:
virtual void Visit( VisitedClass & ) = 0;
};


class BaseVisitable
{
public:
virtual ~BaseVisitable() {}
virtual void Accept( BaseVisitor & ) = 0;
protected:
BaseVisitable() {}
};

class Shape : public BaseVisitable
{
public:
virtual void Accept( BaseVisitor & vtr ) = 0;
};

class Rectangle : public Shape
{
public:
virtual void Accept( BaseVisitor & vtr )
{
Visitor< Rectangle > * const pVisitor =
dynamic_cast< Visitor< Rectangle > * >( &vtr );

if ( pVisitor != 0 )
{
pVisitor->Visit( *this );
}
}
};

class Circle : public Shape
{
public:
virtual void Accept( BaseVisitor & vtr )
{
Visitor< Circle > * const pVisitor =
dynamic_cast< Visitor< Circle > * >( &vtr );

if ( pVisitor != 0 )
{
pVisitor->Visit( *this );
}
}
};


class MyDrawingVisitor : public BaseVisitor, public Visitor< Circle >,
public Visitor< Rectangle > {
public:
void Visit( Rectangle & )
{
std::cout << "Rectangle\n";
}

void Visit( Circle & )
{
std::cout << "Circle\n";
}
};

int main()
{
std::vector< Shape * > vec;

// This is just an example, the following will be leaked
vec.push_back( new Circle );
vec.push_back( new Rectangle );

MyDrawingVisitor visitor;

// iterate over whole vector
for( std::vector< Shape * >::iterator pShape = vec.begin();
pShape != vec.end(); ++pShape )
{
( *pShape )->Accept( visitor );
}

return 0;
}

"Gregory Pakosz" <gpa...@ireste.fr> wrote in message

news:b16s9h$hj8$1...@news-reader10.wanadoo.fr...

Gregory Pakosz

unread,
Feb 3, 2003, 2:32:15 PM2/3/03
to
To Andreas and Chris,

Using typeifo or dynamic_cast implies that i have to enable RTTI.
What is the overhead of enabling this ?
A lot of people say that C++ RTTI dramaticaly drops down performance, and
that it's better to write your own RTTI.
I'm not convinced that writing my own RTTI will be more efficient : to my
mind, this is the same question as : "should I write my own string class
? --> NO, STL is good, you must use it"

What is your opinion about this ?

Regards,

Chris Theis

unread,
Feb 4, 2003, 12:03:01 PM2/4/03
to

"Gregory Pakosz" <gpa...@ireste.fr> wrote in message
news:b1ldbu$13t$1...@news-reader10.wanadoo.fr...

> To Andreas and Chris,
>
> Using typeifo or dynamic_cast implies that i have to enable RTTI.
> What is the overhead of enabling this ?
> A lot of people say that C++ RTTI dramaticaly drops down performance, and
> that it's better to write your own RTTI.
> I'm not convinced that writing my own RTTI will be more efficient : to my
> mind, this is the same question as : "should I write my own string class
> ? --> NO, STL is good, you must use it"
>
> What is your opinion about this ?
>
> Regards,
> Gregory

Hi Gregory,

RTTI actually increases the size of the data per class because it needs one
additional entry in the class vtable and some more storage for the
type_info. Nevertheless for example vtables also need additional (and mostly
unnoticable) memory and IMHO it's very unlikely that you'll run into memory
problems because of that. If you decide to create your own RTTI then each
object must carry some kind of type ID and you need to track all objects.
Furthermore you'd have to emulate the RTTI using switch or if/else
statements which means that you create a huge bulk of code which will
probably be less efficient, slower and less robust than the compiler
generated code.

IMHO if there aren't any special reasons that force you to avoid "built-in"
RTTI it stands to good reason to go with the language features. Probably
there might be another solution for your problem which does not use
dynamic_cast but at least I couldn't think of any other after meditating
about the problem. If somebody comes up with a better idea, please let me
know.


Hope that helps
Chris

Andreas Huber

unread,
Feb 4, 2003, 12:10:19 PM2/4/03
to
Gregory,

> Using typeifo or dynamic_cast implies that i have to enable RTTI.
> What is the overhead of enabling this ?

That hugely depends on your platform. Implementing dynamic_cast always
involves space/time tradeoffs. That is, the more memory (mostly executable
size) you are willing to spend, the faster it gets and vice versa. So, it
pretty much depends on which of those two resources your compiler vendor
thinks is more scarce. I could imagine that optimization for speed/size has
an effect of exactly how dynamic_cast is implemented but I don't know
whether this is supported by existing compilers (any clues anyone?).

> A lot of people say that C++ RTTI dramaticaly drops down performance, and
> that it's better to write your own RTTI.

Yes, there are examples where it can make sense to refrain from using a
particular C++ feature, because you _know_ you can implement it faster,
tigher or both. See for example:
http://www.boost.org/libs/function/index.html#design
http://www.cuj.com/experts/2006/alexandr.htm?topic=experts

Both use function pointers instead of virtual functions, the former to
battle virtual function bloat and the latter to reduce the number of pointer
dereferences. However, as Andrei Alexandrescu notes in the mentioned
article:
"Allow me a parenthetical remark. If you wonder whether I forgot the two
laws of optimizations: (1) don't do it and (2) don't do it yet - well, I
still remember them. Why, then, is Variant so keen on optimization? The
reason is simple. This is not application code; this is a library artifact,
and more so, it is one that emulates and rivals a language feature."

I think this is a very good guideline. Most competent engineers wouldn't
even think about implementing a language feature themselves unless they have
very hard data (profiling etc.) that:
a) The feature is indeed a memory hog/performance bottle neck in their
application.
b) The feature _can_ indeed be implemented tighter/faster.
c) it is _worth_ doing so in dollars and cents.

I think c) is very important, because implementing a language feature
yourself almost always increases the necessary development and testing
effort. You have to get that back somehow.

Regards,

Andreas

0 new messages