Наследовать интерфейсы, которые разделяют имя метода - PullRequest
54 голосов
/ 05 января 2010

Есть два базовых класса с одинаковыми именами функций. Я хочу унаследовать их обоих и перебрать каждый метод по-разному. Как я могу сделать это с отдельным объявлением и определением (вместо определения в определении класса)?

#include <cstdio>

class Interface1{
public:
    virtual void Name() = 0;
};

class Interface2
{
public:
    virtual void Name() = 0;
};

class RealClass: public Interface1, public Interface2
{
public:
    virtual void Interface1::Name()
    {
        printf("Interface1 OK?\n");
    }
    virtual void Interface2::Name()
    {
        printf("Interface2 OK?\n");
    }
};

int main()
{
    Interface1 *p = new RealClass();
    p->Name();
    Interface2 *q = reinterpret_cast<RealClass*>(p);
    q->Name();
}   

Мне не удалось переместить определение в VC8. Я обнаружил, что ключевое слово для Microsoft __interface может успешно выполнять эту работу, код ниже:

#include <cstdio>

__interface Interface1{
    virtual void Name() = 0;
};

__interface Interface2
{
    virtual void Name() = 0;
};

class RealClass: public Interface1,
                public Interface2
{
public:
    virtual void Interface1::Name();
    virtual void Interface2::Name();
};

void RealClass::Interface1::Name()
{
    printf("Interface1 OK?\n");
}

void RealClass::Interface2::Name()
{
    printf("Interface2 OK?\n");
}

int main()
{
    Interface1 *p = new RealClass();
    p->Name();
    Interface2 *q = reinterpret_cast<RealClass*>(p);
    q->Name();
}  

но есть ли другой способ сделать что-то более общее, что будет работать в других компиляторах?

Ответы [ 5 ]

66 голосов
/ 05 января 2010

Эта проблема возникает не очень часто. Решение, с которым я знаком, было разработано Дагом Макилрой и представлено в книгах Бьярна Страуструпа (представленных в Design & Evolution of C ++ раздел 12.8 и Язык программирования C ++ раздел 25.6). Согласно обсуждению в Design & Evolution , было предложено элегантно обработать этот конкретный случай, но он был отклонен, поскольку «такие конфликты имен вряд ли стали достаточно распространенными, чтобы гарантировать отдельную языковую функцию», и «вряд ли станет повседневной работой для новичков».

Вам нужно не только вызывать Name() через указатели на базовые классы, вам нужен способ сказать , какой Name() вы хотите при работе с производным классом. Решение добавляет некоторую косвенность:

class Interface1{
public:
    virtual void Name() = 0;
};

class Interface2{
public:
    virtual void Name() = 0;
};

class Interface1_helper : public Interface1{
public:
    virtual void I1_Name() = 0;
    void Name() override
    {
        I1_Name();
    }
};

class Interface2_helper : public Interface2{
public:
    virtual void I2_Name() = 0;
    void Name() override
    {
        I2_Name();
    }
};

class RealClass: public Interface1_helper, public Interface2_helper{
public:
    void I1_Name() override
    {
        printf("Interface1 OK?\n");
    }
    void I2_Name() override
    {
        printf("Interface2 OK?\n");
    }
};

int main()
{
    RealClass rc;
    Interface1* i1 = &rc;
    Interface2* i2 = &rc;
    i1->Name();
    i2->Name();
    rc.I1_Name();
    rc.I2_Name();
}

Не красиво, но решение состояло в том, что оно не нужно часто.

6 голосов
/ 05 января 2010

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

Примерно так:

template<class id>
class InterfaceHelper : public MyInterface
{
    public : 

       virtual void Name() 
       {
          Name(id);
       }

       virtual void Name(
          const size_t id) = 0;  
}

Затем вы производите от InterfaceHelper дважды, а не от MyInterface дважды, и вы указываете разные id для каждого базового класса. Затем вы можете раздать два интерфейса независимо, приведя к правильному InterfaceHelper.

Вы могли бы сделать что-то немного более сложное;

class InterfaceHelperBase
{
    public : 

       virtual void Name(
          const size_t id) = 0;  
}


class InterfaceHelper1 : public MyInterface, protected InterfaceHelperBase
{
    public : 

       using InterfaceHelperBase::Name;

       virtual void Name() 
       {
          Name(1);
       }
}

class InterfaceHelper2 : public MyInterface, protected InterfaceHelperBase
{
    public : 

       using InterfaceHelperBase::Name;

       virtual void Name() 
       {
          Name(2);
       }
}

class MyClass : public InterfaceHelper1, public InterfaceHelper2
{
    public :

      virtual void Name(
          const size_t id)
      {
          if (id == 1) 
          {
              printf("Interface 1 OK?");
          }
          else if (id == 2) 
          {
              printf("Interface 2 OK?");
          }
      }  
}

Обратите внимание, что выше не видел компилятор ...

6 голосов
/ 05 января 2010

Вы не можете переопределить их по отдельности, вы должны переопределить оба сразу:

struct Interface1 {
  virtual void Name() = 0;
};

struct Interface2 {
  virtual void Name() = 0;
};

struct RealClass : Interface1, Interface2 {
  virtual void Name();
};
// and move it out of the class definition just like any other method:
void RealClass::Name() {
  printf("Interface1 OK?\n");
  printf("Interface2 OK?\n");
}

Вы можете моделировать индивидуальное переопределение с помощью промежуточных базовых классов:

struct RealClass1 : Interface1 {
  virtual void Name() {
    printf("Interface1 OK?\n");
  }
};

struct RealClass2 : Interface2 {
  virtual void Name() {
    printf("Interface2 OK?\n");
  }
};

struct RealClass : RealClass1, RealClass2 {
  virtual void Name() {
    // you must still decide what to do here, which is likely calling both:
    RealClass1::Name();
    RealClass2::Name();

    // or doing something else entirely

    // but note: this is the function which will be called in all cases
    // of *virtual dispatch* (for instances of this class), as it is the
    // final overrider, the above separate definition is merely
    // code-organization convenience
  }
};

Кроме того, вы используете reinterpret_cast неправильно, вы должны иметь:

int main() {
  RealClass rc; // no need for dynamic allocation in this example

  Interface1& one = rc;
  one.Name();

  Interface2& two = dynamic_cast<Interface2&>(one);
  two.Name();

  return 0;
}

А вот переписать с CRTP , который может быть тем, что вы хотите (или нет):

template<class Derived>
struct RealClass1 : Interface1 {
#define self (*static_cast<Derived*>(this))
  virtual void Name() {
    printf("Interface1 for %s\n", self.name.c_str());
  }
#undef self
};

template<class Derived>
struct RealClass2 : Interface2 {
#define self (*static_cast<Derived*>(this))
  virtual void Name() {
    printf("Interface2 for %s\n", self.name.c_str());
  }
#undef self
};

struct RealClass : RealClass1<RealClass>, RealClass2<RealClass> {
  std::string name;
  RealClass() : name("real code would have members you need to access") {}
};

Но учтите, что здесь вы не можете вызывать Name в RealClass сейчас (при виртуальной диспетчеризации, например, rc.Name()), вы должны сначала выбрать базу. Сам макрос является простым способом очистки приведений CRTP (как правило, доступ к элементам гораздо более распространен в базе CRTP), но он может быть улучшен . В одном из моих других ответов есть краткое обсуждение виртуальной рассылки, но, конечно, лучше, если у кого-то есть ссылка.

3 голосов
/ 06 января 2010
class BaseX
{
public:
    virtual void fun()
    {
        cout << "BaseX::fun\n";
    }
};

class BaseY
{
public:
    virtual void fun()
    {
        cout << "BaseY::fun\n";
    }
};


class DerivedX : protected BaseX
{
public:
    virtual void funX()
    {
        BaseX::fun();
    }
};

class DerivedY : protected BaseY
{
public:
    virtual void funY()
    {
        BaseY::fun();
    }
};


class DerivedXY : public DerivedX, public DerivedY
{

};
1 голос
/ 06 января 2010

Есть два других связанных вопроса, задающих почти (но не полностью) идентичные вещи:

Выбор из унаследованных имен общих методов . Если вы хотите, чтобы rc.name () вызывал ic1-> name () или ic2-> name ().

Переопределение имен общих методов из (шаблонных) базовых классов . Это имеет более простой синтаксис и меньше кода, чем ваше принятое решение, но не допускает доступ к функциям из производного класса. Более или менее, если вам не нужно вызывать name_i1 () из rc, вам не нужно использовать такие вещи, как InterfaceHelper.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...