"Bertel Brander" <bertel@post4.tele.dk> wrote in message
news:435c150a$0$198$edfadb0f@dread11.news.tele.dk...
[8<8<8<]
> enum ObjectTypeEnum
> {
> AType,
> BType
> };
Der 2 vedligeholdelsesproblemer med den konstruktion:
* Den skal vedligeholder manuelt, og tildelingen til ObjectType skal
udføres manuelt
* Man får et globalt koblingspunkt, således at hver gang der tilføjes en
ny type skal samtlige type oversættes (ligesom den almindelige visitor
design pattern implementering)
[8<8<8<]
> Base *base = (Base *)p[idx];
> if(base->ObjectType == AType)
> std::cout << ((A *)p[idx])->a << std::endl;
> else if(base->ObjectType == BType)
> std::cout << ((B *)p[idx])->b << std::endl;
> else
> std::cout << "Ups" << std::endl;
Hvis man bruger switch bliver kørselstiden formodentlig O(1), hvor den viste
løsning nemt bliver O(n).
En lidt værre ting er at switch på typen skal dupleres og vedligeholdes alle
steder hvor den bruges.
Jeg lavede for 4 år siden noget kode som følge af et spørgsmål fra Scott
Meyers, der ved hjælp af template metaprogrammering kunne lave runtime
polymorphi under stærke begrænsninger på:
* uden brug af virtuelle metoder
* uden brug af RTTI
* uden at skulle vedligeholde en enum
* uden at frameworket afhænger af applikationskoden
* som kører _meget_ effektiv - mere effektivt end visitor patternet
Scott Meyers problem bestod i at man skulle parse noget tekst, som man så
fik en række tokens ud af. De forskellige type tokens skulle så behandles
forskelligt.
Metoden er velegnet som alternativ til visitor design patternes, hvis man
har et rimeligt statisk antal typer men et over tid variende antal
algoritmer.
Den er naturligvis også velegnet hvis man af en eller anden grund ikke vil
bruge almindelige virtuelle metoder eller RTTI.
Som udgangspunkt vil jeg dog nok almindeligvis benyttes visitor patternet i
en eller anden form - det er simplere at forstå og mere udbredt.
Koden er delt op i 5 blokke:
1. noget type_list erklæringer
2. noget external polymorphism framework
3. nogle applikations typer
4. nogle funktioner der arbejder på applikations typerne
5. et lille test program der bruger det
Det anbefales at læse blokkene i omvendt rækkefølge. Det er ikke trivielt,
men ganske interessant.
I koden står at det ikke oversætter med Microsoft Visual C++ V7.0, men det
oversætter med V7.1 (Visual Studio .NET 2003) og V8.0 (Visual Studio .NET
2005).
// File: scott_token.cpp
// Date:
//
// Example of "External Polymorphism" with
// generativ programming for automaticly creating jumptables.
// Highly optimized for execution speed.
//
// Solution to question by Scott Meyers on newsgroup
// comp.lang.c++.moderated
//
// This file has been compiled with
// Comeau C/C++ 4.2.45.2
// Borland C++Builder 5 Patch 1 on MS-Windows 2000
// Borland C++Builder 6 on MS-Windows XP
// Borland C++Builder 6 Patch 4 on MS-Windows XP
// Intel C++ 5.0.1 on MS-Windows 2000
// gcc 2.96 on Red Hat Linux 7.1
//
// but does not compile with
// Microsoft Visual C++ V6SP4 on MS-Windows 2000
// Microsoft Visual C++ V7.0 on MS-Windows 2000
#include <iostream>
#include <vector>
#include <algorithm>
#if defined(__INTEL_COMPILER) && (500 == __INTEL_COMPILER) &&
defined(_WIN32)
// Intel C++ V5.0 for MS-Windows
// Disable warnings due to debug information truncation, due to Microsoft
compatiblity
#pragma warning(disable : 4786)
#endif
#if defined(__BORLANDC__)
// Borland C++
// Disable warning: Functions containing 'statement' are not expanded inline
#pragma warn -inl
#endif
// Some typelist stuff
// Very much like
// Modern C++ Design
// Andrei Alexandrescu
// ISBN 0-201-70431
// but without preprocessor macros
class null_type;
template <class T, class U>
struct type_list_impl
{
typedef T head;
typedef U tail;
};
template <typename T1, typename T2 = null_type, typename T3 = null_type>
struct type_list;
template <typename T1, typename T2>
struct type_list<T1, T2, null_type>
{
typedef type_list_impl<T1, typename type_list<T2>::list >
list;
};
template <typename T1>
struct type_list<T1, null_type, null_type>
{
typedef type_list_impl<T1, null_type>
list;
};
//external polymorphic framework
template <typename T>
class external_polymorphic
{
public:
unsigned id() const;
static unsigned max_class_id();
protected:
external_polymorphic(unsigned id);
static unsigned register_class();
private:
static unsigned& class_count();
private:
const unsigned id_;
};
template <typename T, typename D>
class reg_external_polymorphic : public T
{
public:
static unsigned class_id();
protected:
reg_external_polymorphic();
template <typename ARG1>
reg_external_polymorphic(ARG1 arg1);
template <typename ARG1, typename ARG2>
reg_external_polymorphic(ARG1 arg1, ARG2 arg2);
template <typename ARG1, typename ARG2, typename ARG3>
reg_external_polymorphic(ARG1 arg1, ARG2 arg2, ARG3 arg3);
template <typename ARG1, typename ARG2, typename ARG3, typename ARG4>
reg_external_polymorphic(ARG1 arg1, ARG2 arg2, ARG3 arg3, ARG4 arg4);
~reg_external_polymorphic();
private:
// ensure that class_id is called, at latest before main is entered
// so that all classes are registered
static const unsigned class_id_;
};
template <typename T, typename D>
const unsigned reg_external_polymorphic<T,D>::class_id_ =
reg_external_polymorphic<T,D>::class_id();
template <typename T>
inline external_polymorphic<T>::external_polymorphic(unsigned id) :
id_(id)
{
T* t = static_cast<T*>(this);
t; // used
}
template <typename T>
inline unsigned external_polymorphic<T>::id() const
{
return id_;
}
template <typename T>
inline unsigned external_polymorphic<T>::max_class_id()
{
return class_count();
}
template <typename T>
inline unsigned& external_polymorphic<T>::class_count()
{
static unsigned class_count_ = 0;
return class_count_;
}
// made non-inline due to bug in C++Builder V6.0 code generation
template <typename T>
#if defined(__BORLANDC__)
#else
inline
#endif
unsigned external_polymorphic<T>::register_class()
{
return class_count()++;
}
template <typename T, typename D>
inline reg_external_polymorphic<T, D>::reg_external_polymorphic() :
T(class_id())
{
}
template <typename T, typename D>
template <typename ARG1>
inline reg_external_polymorphic<T, D>::reg_external_polymorphic(ARG1 arg1) :
T(class_id(), arg1)
{
}
template <typename T, typename D>
template <typename ARG1, typename ARG2>
inline reg_external_polymorphic<T, D>::reg_external_polymorphic(ARG1 arg1,
ARG2 arg2) :
T(class_id(), arg1, arg2)
{
}
template <typename T, typename D>
template <typename ARG1, typename ARG2, typename ARG3>
inline reg_external_polymorphic<T, D>::reg_external_polymorphic(ARG1 arg1,
ARG2 arg2, ARG3 arg3) :
T(class_id(), arg1, arg2, arg3)
{
}
template <typename T, typename D>
template <typename ARG1, typename ARG2, typename ARG3, typename ARG4>
inline reg_external_polymorphic<T, D>::reg_external_polymorphic(ARG1 arg1,
ARG2 arg2, ARG3 arg3, ARG4 arg4) :
T(class_id(), arg1, arg2, arg3, arg4)
{
}
template <typename T, typename D>
inline reg_external_polymorphic<T, D>:
reg_external_polymorphic()
{
// D must be derived from this class
reg_external_polymorphic<T, D>* d = static_cast<D*>(0);
d; //used!
// This class must be derived from external_polymorphic<T>
external_polymorphic<T>* ept = this;
ept; //used!!
&class_id_; // ensure class_id_ is reference and not optimized away !
}
template <typename T, typename D>
inline unsigned reg_external_polymorphic<T, D>::class_id()
{
static const unsigned class_id_ = register_class();
return class_id_;
}
// The token framework
class token : public external_polymorphic<token>
{
public:
// the destructor _need_ not be virtual
// we do not rely on RTTI
// virtual ~token() {}
protected:
// destructor made protected to prevent
token(unsigned id) :
external_polymorphic<token>(id) {}
private:
// copy constructor and copy assignment not implemented
token(const token&);
token& operator=(const token&);
};
// "external polymorphism"
// see Bjarne Stroustrup slides
// "Design Decisions for a Library"
//
http://www.klid.dk/arrangementer/XTI_kbh.pdf
// slide 14-18
//
// this vcall template class is modelled after those slides
// helper classes for vcall
template <typename T1>
struct do_static_cast
{
template <typename T2>
T2 do_cast(T1 t)
{ return static_cast<T2>(t); }
};
template <typename T1>
struct do_dynamic_cast
{
template <typename T2>
T2 do_cast(T1 t)
{ return dynamic_cast<T2>(t); }
};
template <typename Func, typename DispatchTypelist, typename CastPolicy =
do_static_cast<const token&> >
struct func_table_generator
{
typedef typename Func::return_type return_type;
typedef return_type (*func_ptr_type)(const token&);
typedef std::vector<func_ptr_type> func_vector_type;
static void fill_func_vector(func_vector_type& func_vector)
{ func_table_generator<Func, typename
DispatchTypelist::list>::fill_func_vector(func_vector); }
static unsigned max_class_id()
{ return func_table_generator<Func, typename
DispatchTypelist::list>::max_class_id(); }
};
template <typename Func, typename T, typename CastPolicy>
struct func_table_generator<Func, type_list_impl<T, null_type>, CastPolicy>
{
typedef typename Func::return_type return_type;
typedef return_type (*func_ptr_type)(const token&);
typedef std::vector<func_ptr_type> func_vector_type;
static return_type do_call(const token& tok)
{
return Func()(CastPolicy().do_cast<const T&>(tok));
}
static void fill_func_vector(func_vector_type& func_vector)
{
func_vector[T::class_id()] = do_call;
}
static unsigned max_class_id()
{
return T::max_class_id();
}
};
template <typename Func, typename T, typename U, typename CastPolicy>
struct func_table_generator<Func, type_list_impl<T, U>, CastPolicy>
{
typedef typename Func::return_type return_type;
typedef return_type (*func_ptr_type)(const token&);
typedef std::vector<func_ptr_type> func_vector_type;
static return_type do_call(const token& tok)
{
return Func()(CastPolicy().do_cast<const T&>(tok));
}
static void fill_func_vector(func_vector_type& func_vector)
{
func_vector[T::class_id()] = do_call;
func_table_generator<Func, typename U>::fill_func_vector(func_vector);
}
static unsigned max_class_id()
{
return T::max_class_id();
}
};
template <typename ReturnType>
struct pure_virtual_throw
{
static ReturnType pure_virtual()
{ throw 0; /* or something more sensible */ }
template <typename ARG0>
static ReturnType pure_virtual(ARG0 /*arg0*/)
{ throw 0; /* or something more sensible */ }
template <typename ARG0, typename ARG1>
static ReturnType pure_virtual(ARG0 /*arg0*/, ARG1 /*arg1*/)
{ throw 0; /* or something more sensible */ }
template <typename ARG0, typename ARG1, typename ARG2>
static ReturnType pure_virtual(ARG0 /*arg0*/, ARG1 /*arg1*/, ARG2
/*arg2*/)
{ throw 0; /* or something more sensible */ }
template <typename ARG0, typename ARG1, typename ARG2, typename ARG3>
static ReturnType pure_virtual(ARG0 /*arg0*/, ARG1 /*arg1*/, ARG3
/*arg3*/)
{ throw 0; /* or something more sensible */ }
};
template <typename ReturnType>
struct pure_virtual_nothing
{
static ReturnType pure_virtual()
{ }
template <typename ARG0>
static ReturnType pure_virtual(ARG0 /*arg0*/)
{ }
template <typename ARG0, typename ARG1>
static ReturnType pure_virtual(ARG0 /*arg0*/, ARG1 /*arg1*/)
{ }
template <typename ARG0, typename ARG1, typename ARG2>
static ReturnType pure_virtual(ARG0 /*arg0*/, ARG1 /*arg1*/, ARG2
/*arg2*/)
{ }
template <typename ARG0, typename ARG1, typename ARG2, typename ARG3>
static ReturnType pure_virtual(ARG0 /*arg0*/, ARG1 /*arg1*/, ARG3
/*arg3*/)
{ }
};
template <typename Func, typename DispatchTypelist, typename
PureVirtualPolicy = pure_virtual_throw<typename Func::return_type> >
struct func_table
{
typedef typename Func::return_type return_type;
typedef return_type (*func_ptr_type)(const token&);
typedef std::vector<func_ptr_type> func_vector_type;
func_table();
const func_vector_type& func_vector() const
{ return func_vector_; }
static return_type pure_virtual(const token& tok)
{ PureVirtualPolicy::pure_virtual<const token&>(tok); }
private:
func_vector_type func_vector_;
private:
func_table(const func_table&);
func_table& operator=(const func_table&);
};
// constructor is not inlined - only called once
// code bloat if inlined
template <typename Func, typename DispatchTypelist, typename
PureVirtualPolicy>
func_table<Func, DispatchTypelist, PureVirtualPolicy>::func_table() :
func_vector_(func_table_generator<Func,
DispatchTypelist>::max_class_id()+1, pure_virtual)
{
func_table_generator<Func,
DispatchTypelist>::fill_func_vector(func_vector_);
}
template <typename DispatchTypelist, typename Func>
inline typename Func::return_type vcall(const token& tok)
{
typedef func_table<Func, DispatchTypelist>
func_table_type;
typedef typename func_table_type::func_vector_type
func_vector_type;
// jump-table initialized first time vcall is called
static const func_table_type func_tbl;
// Check for "tok.id() >= func_tbl.func_vector()" not needed
// because all classes are registered before main is entered
// Does not work if classes are loaded dynamic -
// like with .DLL or .so files
return (*func_tbl.func_vector()[tok.id()])(tok);
}
// application code
class special_token_1 : public reg_external_polymorphic<token,
special_token_1>
{
public:
special_token_1() {}
};
class special_token_2 : public reg_external_polymorphic<token,
special_token_2>
{
public:
special_token_2() {}
};
class special_token_3 : public reg_external_polymorphic<token,
special_token_3>
{
public:
special_token_3() {}
};
struct func_process
{
public:
typedef void return_type;
template <class T>
return_type operator()(const T& t)
{ return process(t); }
};
inline void process(const token& tok)
{
typedef type_list<
special_token_1,
special_token_2>
token_type_list;
return vcall<token_type_list, func_process>(tok);
}
template <class T>
void process(const T&); // not implemented - catch non exact type match a
link time
inline void process(const special_token_1& tok)
{
using std::cout;
using std::endl;
cout << "processing special_token_1" << endl;
}
inline void process(const special_token_2& tok)
{
using std::cout;
using std::endl;
cout << "processing special_token_2" << endl;
}
int main(int argc, char* argv[])
{
using std::cerr; using std::cout;
using std::endl;
special_token_1 st1;
special_token_2 st2;
special_token_3 st3;
token* tok = &st1;
process(*tok);
tok = &st2;
process(*tok);
tok = &st3;
try {
process(*tok);
cerr << "Error: unknown token type not detected!!!" << endl;
}
catch(...) {
cout << "special_token_3 is correctly not known :)" << endl;
}
}
Venlig hilsen
Mogens Hansen