Какой самый простой способ удовлетворить чистый абстрактный метод с помощью методов из других базовых классов - PullRequest
8 голосов
/ 12 ноября 2011

Редактировать: Под некоторыми комментариями под простым я подразумеваю: а) меньше кода, б) легко поддерживать и в) трудно ошибиться.

Редактирование # 2: Кроме того, использование сдерживания вместо частного наследования не является нежелательным, если оно действительно упрощает реализацию InterfaceImpl.

В настоящее время единственный способ, которым я знаю, - это заставить реализатор определить абстрактный метод и делегировать вызов методу целевого базового типа. Пример:

#include <iostream>
#include <memory>

class Interface
{
public:
    virtual void method1() = 0;
    virtual void method2(int x) = 0;
};

class MethodOneImpl
{
 private:
    void method1(int x)
    { std::cout << "MethodOneImpl::method1() " << x << std::endl; }

 public:
    void method1() { method1(0); }
};

class MethodTwoImpl
{
 public:
    void myFunc(int x)
    { std::cout << "MethodTwoImpl::myFunc(x)" << x << std::endl; }
};

class InterfaceImpl : public Interface
                    , private MethodOneImpl
                    , private MethodTwoImpl
{
public:    
    virtual void method1() { MethodOneImpl::method1(); }
    virtual void method2(int x) { MethodTwoImpl::myFunc(x); }
};

int main()
{
    std::unique_ptr<Interface> inf;
    inf.reset(new InterfaceImpl);
    inf->method1();
    inf->method2(0);

    // This should be disallowed!
    // std::unique_ptr<MethodOneImpl> moi;
    // moi.reset(new InterfaceImpl);
}

Сначала я подумал, что, возможно, это решит проблему:

class InterfaceImpl : public Interface
                    , private MethodOneImpl
                    , private MethodTwoImpl
{
public:    
    using MethodOneImpl::method1;
    // Obviously this wouldn't work as the method names don't match.
    //using MethodTwoImpl::??? 
};

Первый оператор using сделает оба метода MethodOneImpl::method1 общедоступными, но на самом деле он не выполняет контракт с Interface и изменяет доступность MethodOneImpl::method1(int). И очевидно, что мы не можем использовать это решение с method2, поскольку имена не совпадают.

FWIW, у меня есть то, что я считаю решением, но оно вообще не является частью стандарта (другими словами, оно не будет компилироваться). Я думал о внесении предложения в комитет C ++; если у кого-то есть совет, я буду признателен за любые комментарии ниже (но, пожалуйста, не отправляйте совет в качестве ответа).

Ответы [ 5 ]

4 голосов
/ 14 ноября 2011

Другой вариант (по крайней мере, при использовании MS VC ++) - использовать виртуальное наследование:

struct MyInterface
{
    virtual void Method1() = 0;
    virtual void Method2() = 0;
};

class Method1Impl : public virtual MyInterface
{
    virtual void Method1() { _tprintf( _T("Method1\n") ); }
};

class Method2Impl : public virtual MyInterface
{
    virtual void Method2() { _tprintf( _T("Method2\n") ); }
};

class InterfaceImpl : public virtual MyInterface,
                      private Method1Impl,
                      private Method2Impl
{
};

void TestWeirdInterfaceImpl()
{
    MyInterface*    pItf = new InterfaceImpl();

    pItf->Method1();
    pItf->Method2();
}

Хотя это, кажется, работает и удовлетворяет тому, что вы ищете (см. Предупреждение C4250 о том, что у вас будетподавить с помощью #pragma), это не будет моим подходом.(Я считаю, что виртуальное наследование все еще не поддерживается всеми компиляторами, но я могу ошибаться.)

Я бы, вероятно, пошел с защитой, и как только шаблонный код станет идентификатором, оберните его в какую-то макроплану (аналогично картам в ATL или MFC), из-за которых было бы очень, очень трудно когда-либо облажаться.

Так что это мой макроподход:

struct MyInterface
{
    virtual float Method1( int x ) = 0;
    virtual int Method2( float a, float b ) = 0;
    virtual void Method3( const TCHAR* sz ) = 0;
};

class Method1Impl
{
public:
    float Method1( int x ) {
        _tprintf( _T("Method1: %d\n"), x ); return 5.0;
    }
};

class Method2and3Impl
{
public:
    int Method2( float a, float b ) {
        _tprintf( _T("Method2: %f, %f\n"), a, b ); return 666;
    }

    void Method3( const TCHAR* sz ) {
        _tprintf( _T("Method3: %s"), sz );
    }
};


#define DECLARE_METHOD0( MethodName, Obj, R )   \
    virtual R MethodName() { return Obj.MethodName(); }

#define DECLARE_METHOD1( MethodName, Obj, R, A1 )   \
    virtual R MethodName( A1 a1 ) { return Obj.MethodName( a1 ); }

#define DECLARE_METHOD2( MethodName, Obj, R, A1, A2 )   \
    virtual R MethodName( A1 a1, A2 a2 ) { return Obj.MethodName( a1, a2 ); }


class InterfaceImpl : public MyInterface
{
public:
    DECLARE_METHOD1( Method1, m_method1Impl, float, int );
    DECLARE_METHOD2( Method2, m_method2and3Impl, int, float, float );
    DECLARE_METHOD1( Method3, m_method2and3Impl, void, const TCHAR* );

private:
    Method1Impl         m_method1Impl;
    Method2and3Impl     m_method2and3Impl;
};

void TestWeirdInterfaceImpl()
{
    MyInterface*    pItf = new InterfaceImpl();

    pItf->Method1( 86 );
    pItf->Method2( 42.0, 24.0 );
    pItf->Method3( _T("hi") );
}

Пока боги C ++ не утешают насс переменными макросами вам придется объявлять один для каждого количества параметров, которые у вас есть.Также, если вы используете множественное наследование, потенциально вам не понадобится второй параметр «Obj», но, как я уже говорил, я бы избежал множественного наследования, если есть другое решение, которое в данном случае является одним дополнительным параметром.

Тем не менее, третий вариант может быть чем-то, что авторы Pragmatic Programmer , похоже, отстаивают много.Если у вас есть тонна печенья код, который вы не хотите повторять, потому что, как вы указали, он вводит человеческую ошибку.Определите свой собственный язык и напишите сценарий генератора кода (python, perl ...) для автоматического создания реального кода.В этом случае вы можете почти указать на интерфейс, и сценарий напишет за вас текст.Я сам не пытался делать подобные вещи, но в последнее время хотел использовать это где-то, чтобы увидеть и оценить результат.

0 голосов
/ 17 ноября 2011

Кажется невозможным ввести MethodOneImpl / MethodTwoImpl в область действия Interface без того, чтобы они наследовали от Interface, потому что они не будут заполнять виртуальную таблицу, если они этого не делают. C ++ упускает что-то вроде ключевого слова implements из других языков.

Таким образом, вы застряли с виртуальным наследованием , если не понимаете / не принимаете, что то, что вы ищете, это просто шаблон моста , который не удовлетворяет требованию a) (вы должны напишите больше кода), в середине b) (код не обязательно сложен в обслуживании) и может удовлетворить c).

Здесь (другое) возможное решение (с единственным методом, хотя уменьшить вздутие живота)

class Interface 
{ public:
 virtual void method1() {return impl_->method1();}
 private:
     Interface() {}
 protected:
     struct Impl {
         virtual void method1() = 0; };
     std::shared_ptr<Impl> impl_;
     Interface(const std::shared_ptr<Impl> &impl) : impl_(impl) {}
};

  class InterfaceImpl : public Interface
{
  struct Impl : public Interface::Impl {
      void method1()  { std::cout << "InterfaceImpl::method1() " << std::endl; }  } ;
public:
    InterfaceImpl() : Interface(std::shared_ptr<Impl> (new Impl)) {}
      };

  template <class T>
  class GenericInterfaceImpl :  public Interface {
      struct Impl : public Interface::Impl {
          Impl( T &t) : t_(t) {}
          void method1() { t_.method1() ; } 
          T t_; };
public:
    GenericInterfaceImpl() : Interface(std::shared_ptr<Impl> (new Impl(T()))) {}
      };

 struct AMethod1Impl {
     void method1() { std::cout << "AMethod1Impl::method1() " << std::endl; }  } ;

struct AnotherMethod1Impl_not_working {
     void method1_not_present() { std::cout << "AnotherMethod1Impl_not_working ::method1_not_present() " << std::endl; }  } ;

int main() {
 // compilation of next line would fail 
 // (lame attempt to simulate ompilation fail when pure function not implemented)
 // Interface inf;

 std::unique_ptr<Interface> inf;
 inf.reset(new InterfaceImpl);
 inf->method1();
 inf.reset(new GenericInterfaceImpl<AMethod1Impl>() );
 inf->method1();

 // compilation of next line would fail
 // inf.reset(new GenericInterfaceImpl<AnotherMethod1Impl_not_working>() );

    }
0 голосов
/ 16 ноября 2011

Служит ли это вашей цели? Он поддерживает взаимосвязь интерфейса и предоставляет вам поддерживаемый код без учета кода клиента.

Разделение каждого метода в functionoid и предоставление вам возможности управлять прототипом каждого метода другого базового класса.

#include <iostream>
#include <memory>
using namespace std;

    //No Control over this.
    class MethodOneImpl
    {
     private:
        void method1(int x)
        { std::cout << "MethodOneImpl::method1() " << x << std::endl; }

     public:
        void method1() { method1(0); }
    };

    class MethodTwoImpl
    {
     public:
        void myFunc(int x)
        { std::cout << "MethodTwoImpl::myFunc(x)" << x << std::endl; }
    };

    //*************************//

    class Interface
    {
    public:
        virtual void method1() = 0;
        virtual void method2(int x) = 0;
    };

    //This is what i would do. //

    class BaseFuncType
    {
    //no pure virtual
    void Call()
          {
              throw "error";
          }
    void Call(int x)
          {
              throw "error";
          }
    };


    class Method1: public BaseFuncType
    {
    auto_ptr<MethodOneImpl> MethodPtr;
    public:
    Method1()
    {
        MethodPtr.reset(new MethodOneImpl());
    }
    virtual int Call() 
    {
        MethodPtr->method1(); 
    }
    };

    class Method2: public BaseFuncType
    {
        auto_ptr<MethodTwoImpl> MethodPtr;
    public:
    Method2()
    {
       MethodPtr.reset(new MethodTwoImpl());
    }
    virtual int Call(int x) 
    {
        MethodPtr->myFunc(x);
    }
    };
    template <class T1>
    class MethodFactory
    {
    private:
       T1 methodObj;
    public:
    void CallMethod()
    {
       methodObj.Call();
    }
    void CallMethod(int x)
    {
       methodObj.Call(x);
    }

    };
    class InterfaceImpl : public Interface
    {
        auto_ptr<MethodFactory> factory;
    public:
        virtual void method1() 
        {   
            factory.reset(new MethodFactory<Method1>());
            factory->CallMethod(); 
        }
        virtual void method2(int x) 
        { 
            factory.reset(new MethodFactory<Method2>());
            factory->CallMethod(x); 
        }
    };

    int main()
    {
        auto_ptr<Interface> inf;
        inf.reset(new InterfaceImpl);
        inf->method1();
        inf->method2(10);

        // This should be disallowed!
        // std::unique_ptr<MethodOneImpl> moi;
        // moi.reset(new InterfaceImpl);
    }
0 голосов
/ 15 ноября 2011
class AbsInterface
{
    // this is a simple interface class.
public:
    virtual void Method1() = 0;
    virtual void Method2() = 0;
};

class Functor1
{
public:
    void operator () ()
    {
        printf("This Is Void Functor1");
    }
};

class Functor2
{
public:
    void operator () ()
    {
    printf("This Is void Functor2");
    }
};

template <class T1, class T2>
class DerivedTemplateClass : public AbsInterface
{
public:
    virtual void Method1() { T1()(); }
    virtual void Method2() { T2()(); }
};

void main()
{
    DerivedTemplateClass<Stratege1, Stratege2> instance;
    instance.Method1();
    instance.Method2();
}

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

0 голосов
/ 14 ноября 2011

Это некрасиво и может привести к увеличению размера исполняемого файла, но как насчет

#include <iostream>

class Interface
{
public:
    virtual void method1() = 0;
    virtual void method2(int x) = 0;
};

template<typename T>
class MethodOneImpl : public T
{
 private:
    void method1(int x)
    { std::cout << "MethodOneImpl::method1() " << x << std::endl; }

 public:
    void method1() { method1(0); }
};

template<typename T>
class MethodTwoImpl : public T
{
 public:
    void method2(int x)
    { std::cout << "MethodTwoImpl::myFunc(x)" << x << std::endl; }
};

class InterfaceImpl : public MethodTwoImpl<MethodOneImpl<Interface> >
{
};

int main()
{
    InterfaceImpl impl;
    impl.method1();
    impl.method2(0);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...