As my prveious question sounded confusing, I think it's best to clearly state what I want to achieve.
I have (ignore the inheritance for now and focus on X):
class Base {};
class X : public Base {
private:
double m_double;
public:
template<class A> friend
void state( A& a, const X& x ) {
data( a, x.m_double, "m_double" );
}
};
I can now introduce arbitrary new classes that performs different actions based on how the data function is overloaded, we will refer to these as Accessors in the following:
class XmlArchive {...}; //One Accessor
template<class T>
void data( XmlArchive& a, const T& t, const std::string& str ) {
//read data and serialize it to xml Archive
}
class ParameterList {...}; //Another Accessor
template<class T>
void data( ParameterList& a, const T& t, const std::string& str ) {
//put data in a parameter list
}
I can then write:
X myX;
XmlArchive myArchive;
state( myArchive, myX );
ParameterList myParameters;
state( myParameters, myX );
Fantastic, code reuse! :D However the following (clearly) fails:
Base* basePtr = new X; //This would come from factory really, I should not know the type X
state( myParameters, *basePtr ); //Error
The goal is to make this last call succed. What I have considered (and why it is not acceptable):
First option: make all Accessors inherit from a common base class, say AccessorBase, then write in Base
virtual state( AccessorBase& a ) const = 0;
and implement the required code in X (the syntax to call state will be marginally different but this can be fixed). The problem is that AccessorBase will need to have a virtual function for every possible type which comes as second argument in the data function(s). As these types can be user-defined classes (see case of class composition, X which has Y as data member) I do not see how开发者_如何学JAVA to make this strategy can work.
Second option: create a virtual function in Base for every Accessor. This violates the open/close principle as adding a new Accessor class (say TxtArchive) will require modification of the base class.
I understand why virtual member function cannot be templated (possible different vtbls in different compilation units). However it seemes to me that there should be a solution to this problem... Base knows that it really is of type X, and the type of the Accessor is always explicit, so it is a matter of finding a way of calling (for Accessor of type XmlArchive):
state( xmlArchive, x ); //xmlArchive of type XmlArchive, x of type X
which will yield the result.
To sum-up I would like the call:
state( myParameters, *basePtr );
to succeed if basePtr is pointing to a derived class with a function template compatible with the call and to throw an exception otherwise.
It seemes to me that boost::serialize does something similar but I cannot figure out how (it may be it is re-implemnting inheritance relationships in C++ via templates, I see a This() funcion returning the most derived pointer but it gets really confusing...)
Thank you again in advance for your help!
You could use the vistor pattern for this:
class IVisitor
{
public:
virtual void on_data( double, const char * )=0;
virtual void on_data( std::string, const char * )=0;
};
class Base
{
public:
virtual void visit( IVisitor * v )=0;
};
class X : public Base
{
private:
double m_double;
std::string m_string;
public:
void visit( IVisitor * v)
{
v->on_data( m_double, "m_double" );
v->on_data( m_string, "m_string" );
}
};
Base * base = ...;
XmlArchive ar; // This must derive from IVisitor
base->visit(&ar);
ParameterList pl; // This must derive from IVisitor
base->visit(pl);
If you dislike the need to implement each of the different on_data
types in the IVisitor
, you can use a boost::any
- but in my experience the branching required in the implementations of that function are not worth it.
I believe you can do this using dynamic_cast or a C-style cast, as long as you know you have an object of X, cast Base* to X* and call your method (which should be static in your example.) If you're inheritance chain is deeper and you don't know if you have X, Y, or Z at compile time then you can still do it, but you'll need to enable RTTI.
So to sum up:
X::state( myParameters, *(X*)(basePtr) );
Or if you have RTTI enabled:
X::state( myParameters, *dynamic_cast<X*>(basePtr) );
In the X, Y, Z scenario, you'll need three branches wth Z::state, Y::state, and X::state, calling the right one depending on the run time type of basePtr.
switch(basePtr->get_type())
{
case TYPE_X:
X::state( myParameters, *(X*)(basePtr) );
break;
case TYPE_Y:
Y::state( ... );
break;
}
You get the idea.
Not sure I fully understand the parameters of what you're asking, but my spider-sense is signalling that what you're after is an application of the Curiously Recurring Template Pattern:
class StateBase {
virtual void state() = 0;
};
template<typename T>
class StateMixin : public StateBase {
void state() {
T::data();
}
};
class MyType : public StateMixin<MyType>
{
void data() {};
}
Here, state() is defined with StateMixin and is callable from any StateBase instance, but is separately instantiated by the compiler for each user-defined type implementing data.
Add the friend declaration to your Base so it became
class Base
{
template<class A> friend void state( A& a, const X& x );
};
精彩评论