accès ambigu lors de l'appel d'une classe de base décorée
Question
J'ai une classe qui peut être décorée avec un ensemble de modèles complémentaires pour fournir des fonctionnalités supplémentaires.Chaque module complémentaire doit pouvoir appeler la classe de base et l'utilisateur doit pouvoir appeler la classe de base (soit directement, soit en utilisant CMyClass comme proxy).Malheureusement, le compilateur ne peut pas déterminer quelle classe de base j'appelle et j'obtiens des erreurs d'accès ambiguës.
template< class T >
class AddOn_A : public T
{
public:
AddOn_A( int x ) : T( x )
{};
int AddOne()
{
T* pT = static_cast< T* >( this );
return pT->GetValue() + 1;
};
};
template< class T >
class AddOn_B : public T
{
public:
AddOn_B( int x ) : T( x )
{};
int AddTwo()
{
T* pT = static_cast< T* >( this );
return pT->GetValue() + 2;
};
};
class CBase
{
public:
explicit CBase( int x ) : x_( x )
{
};
int GetValue()
{
return x_;
};
private:
int x_;
};
// define an empty AddOn
template< class > struct empty {};
// forward declaration and Add-On defaults
template< template< class > class AddOn1 = empty,
template< class > class AddOn2 = empty,
template< class > class AddOn3 = empty >
class CMyClass;
// specialized template for the default case
template<> class CMyClass< empty, empty, empty > : public CBase
{
public:
CMyClass( int x ) : CBase( x )
{};
};
// actual definition
template< template< class > class AddOn1,
template< class > class AddOn2,
template< class > class AddOn3 >
class CMyClass : public AddOn1< CBase >,
public CMyClass< AddOn2, AddOn3 >
{
public:
CMyClass( int x ) : AddOn1< CBase >( x ),
CMyClass< AddOn2, AddOn3 >( x )
{};
};
int _tmain( int argc, _TCHAR* argv[] )
{
CMyClass< AddOn_A > A( 100 );
// error C2385: ambiguous access of 'GetValue'
// 1> could be the 'GetValue' in base 'CBase'
// 1> or could be the 'GetValue' in base 'CBase'
_ASSERT( A.GetValue() == 100 );
// error C2385: ambiguous access of 'GetValue'
// 1> could be the 'GetValue' in base 'CBase'
// 1> or could be the 'GetValue' in base 'CBase'
_ASSERT( A.AddOne() == A.GetValue() + 1 );
// works
_ASSERT( A.AddOne() == 101 );
CMyClass< AddOn_A, AddOn_B > AB( 100 );
// same errors as above
_ASSERT( AB.GetValue() == 100 );
// same errors as above
_ASSERT( AB.AddTwo() == AB.GetValue() + 2 );
// works
_ASSERT( AB.AddTwo() == 102 );
return 0;
}
Quelqu'un peut-il m'indiquer ce que je fais de mal ?
Merci, Paulh
La solution
Bon, depuis que je me suis lancé dans l'approche Décorateur, autant bien :)
MODIFIER:ajoutons les AddOnValues pour résoudre ce aussi
Le problème ici est le multi-héritage.Tracer un tel diagramme n'est pas facile, mais si vous regardez attentivement, vous verrez que CMyClass<AddOn_A>
hérite deux fois de CBase.
CMyClass<AddOn_A>
<--AddOn_A<CBase>
<--CBase
CMyClass<AddOn_A>
<--CMyclass<empty,empty,empty>
<--CBase
Le problème est que vous avez utilisé une approche politique au lieu d’une approche décorateur.Dans une approche Decorator appropriée, la hiérarchie est strictement linéaire et vous n'avez qu'un seul paramètre de modèle à la fois.Voyons la base :
// Note that the static_cast are completely unnecessary
// If you inherit from T then you can freely enjoy
// its public and protected methods
template< class T >
class AddOn_A : public T
{
public:
enum { AddOnValues = T::AddOnValues | 0x01 }; // this hides T::AddOnValues
AddOn_A( int x ) : T( x ) {};
int AddOne()
{
return this->GetValue() + 1;
};
};
template< class T >
class AddOn_B : public T
{
public:
enum { AddOnValues = T::AddOnValues | 0x02 }; // this hides T::AddOnValues
AddOn_B( int x ) : T( x ) {};
int AddTwo()
{
return this->GetValue() + 2;
};
};
class CBase
{
public:
enum { AddOnValues = 0x00 };
explicit CBase( int x ) : x_( x ) {}
virtual ~CBase() {} // virtual destructor for inheritance
int GetValue() const { return x_; }; // const method
private:
int x_;
};
Passons maintenant à l'utilisation réelle !
// First, the typedef approach
typedef AddOn_B< AddOn_A< CBase > > CMyClass;
CMyClass myObject(3);
std::cout << myObject.GetValue() << std::endl;
std::cout << myObject.AddOne() << std::endl;
std::cout << myObject.AddTwo() << std::endl;
C'est assez simple, n'est-ce pas ?L'inconvénient évident est que vous n'y ajoutez pas de fonctionnalités...
// I want more!
template < class T >
class CMyClassImpl: public T
{
// Whatever you want
};
CMyClassImpl< AddOn_B< AddOn_A< CBase > > > myObject(3);
D'accord...pas si beau je suppose...Encore mieux ?Eh bien, nous pouvons simplement utiliser un wrapper !
// Even better
template <>
class CMyClass: public CMyClassImpl < CBase > {};
template < template <class> class AddOn1>
class CMyClass: public CMyClassImpl <AddOn1 < CBase > > {};
template < template <class> class AddOn1,
template <class> class AddOn2 >
class CMyClass: public CMyClassImpl < AddOn2 < AddOn1< CBase > > > {};
template < template <class> class AddOn1,
template <class> class AddOn2,
template <class> class AddOn3 >
class CMyClass: public CMyClassImpl < AddOn3 < AddOn2< AddOn1< CBase > > > > {};
// Go on with as much specializations as you wish
CMyClass < AddOn_A, AddOn_B > myObject(3);
Bien sûr, la dernière solution évite de taper sur le site appelant, mais il faut vraiment travailler sa classe :)
De plus, il faut répéter les différents constructeurs à chaque étape de l'héritage, ce que je prouverai rapidement...ennuyeux.
Il existe des macros de préprocesseur, mais...la dernière fois, il m'a fallu environ 500 lignes pour générer quelque chose d'assez simple, alors ne vous embêtez pas à taper, vraiment :)