неоднозначный доступ при вызове декорированного базового класса - PullRequest
1 голос
/ 22 октября 2009

У меня есть класс, который может быть украшен набором дополнительных шаблонов для обеспечения дополнительной функциональности. Каждая надстройка должна иметь возможность вызывать базовый класс, а пользователь должен иметь возможность вызывать базовый класс (либо напрямую, либо используя CMyClass в качестве прокси). К сожалению, компилятор не может сказать, какой базовый класс я вызываю, и я получаю неоднозначные ошибки доступа.

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;
}

Кто-нибудь может указать, что я могу делать неправильно?

Спасибо, PaulH

1 Ответ

1 голос
/ 22 октября 2009

Ну, так как я запустил подход Decorator, я мог бы также:)

РЕДАКТИРОВАТЬ: давайте добавим AddOnValues ​​для решения это , а также

Проблема здесь в мульти-наследовании. Отслеживать такую ​​диаграмму непросто, но если вы присмотритесь, вы увидите, что CMyClass<AddOn_A> дважды наследуется от CBase.

  1. CMyClass<AddOn_A> <- <code>AddOn_A<CBase> <- <code>CBase
  2. CMyClass<AddOn_A> <- <code>CMyclass<empty,empty,empty> <- <code>CBase

Проблема в том, что вы использовали подход политики, а не подход декоратора. При правильном подходе Decorator иерархия строго линейна, и у вас есть только один параметр шаблона за раз. Давайте получим основу:

// 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_;
};

Теперь мы можем перейти к фактическому использованию!

// 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;

Довольно просто, не правда ли? Очевидным недостатком является то, что вы не добавляете туда функциональность ...

 // I want more!
 template < class T >
 class CMyClassImpl: public T
 {
   // Whatever you want
 };

 CMyClassImpl< AddOn_B< AddOn_A< CBase > > > myObject(3);

Хорошо ... не так красиво, я думаю ... Еще лучше? Ну, мы можем просто использовать обертку!

 // 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);

Конечно, последнее решение экономит набор текста на вызывающем сайте, но вы действительно должны работать над своим классом:)

Кроме того, вы должны повторять различные конструкторы на каждом шаге наследования, что мне быстро докажет ... скучно.

Существуют макросы препроцессора, но ... в прошлый раз мне понадобилось около 500 строк, чтобы сгенерировать что-то довольно простое, так что не беспокойтесь и не печатайте, действительно:)

...