Виртуальная функция с фиксированной реализацией, не использующая самый производный класс для (* this) - PullRequest
4 голосов
/ 16 мая 2011

Допустим, у меня есть следующий код:

struct Z;

struct A
{
  virtual void Do (Z & z) const;
};

struct B : public A {};

struct Z
{
  void use (A const & a) {}
  void use (B const & b) {}
};


void A::Do(Z& z) const{
  z.use(*this);
}

Прямо сейчас, когда я вызываю B.do, тип this равен A, что имеет смысл, потому что реализация do определена в A.

Можно ли как-нибудь сделать так, чтобы B.do использовал use (B const &) без необходимости копировать и вставлять тот же код для do из A в B? В моем собственном коде у меня есть около 15 (и растущих) классов, производных от некоторого базового класса, и кажется, что тратить на копирование и вставку идентичного кода для do каждый раз - просто бесполезно.

[Редактировать] Уточнение: все, что Do делает, это вызывает use, ничего больше. Do и use - это функции приема и посещения из шаблона Visitor .

Ответы [ 4 ]

3 голосов
/ 16 мая 2011

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


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

// your Z
class Visitor;

// superclass needed for generic handling
struct Superbase{
  virtual void Accept(Visitor& v) = 0;
};

// your A
template<class Der>
class Base : public Superbase{
public:
    void Accept(Visitor& v){
        v.Visit(static_cast<Der&>(*this));
    }
};

// your B
class Derived1 : public Base<Derived1> {
};

// new C
class Derived2 : public Base<Derived1> {
};

class Visitor {
public:
    virtual void Visit(Superbase& sup){
      // generic handling of any Superbase-derived type
    }

    virtual void Visit(Derived1& d1){
      // handle Derived1
    }

    virtual void Visit(Derived2& d2){
      // handle Derived1
    }
};

int main(){
    Visitor v;
    Derived1 d1;
    d1.Accept(v);
}

Единственная проблема: теперь вы упускаете возможность иметь общий дескриптор для любого типа A, поскольку функции не могут быть как виртуальными, так и шаблонами.: |
Очистите это, нашли решение, используя базовый класс Superbase.:) Это даже позволяет вам иметь контейнер Superbase с и в полной мере использовать полиморфизм.:)

2 голосов
/ 16 мая 2011

Я думаю, что этот код делает то, что вы хотите:

#include <iostream>

struct A;
struct B;

struct Z
{
    void use (A const & a);
    void use (B const & b);
};

template<typename DERIVED>
struct XX
{
    void Do(Z& z){
        Do(z,THIS());
    }
private:
    const DERIVED& THIS() const { return static_cast<const DERIVED&>(*this); }
    void Do(Z& z, const DERIVED& t){
        z.use(t);
    }
};

struct A : public XX<A> {};
struct B : public XX<B> {};

void Z::use (A const & a) { std::cout << "use for A" << std::endl; }
void Z::use (B const & b) { std::cout << "use for B" << std::endl; }

int main(){

    A a;
    B b;
    Z z;
    a.Do(z);
    b.Do(z);
    return 0;
}

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

0 голосов
/ 16 мая 2011

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

Пожалуйста, кто-нибудь, докажите, что я не прав! (Я также вижу, что это решено, чтобы удалить избыточность)

0 голосов
/ 16 мая 2011

Вам нужно отправить вызов use в зависимости от типа, на который указывает this, поэтому вам нужно добавить еще одну виртуальную функцию к A и B, которая просто вызывает правильный use.Я предполагаю, что do делает что-то иное, чем вызов use, конечно, в противном случае вам действительно придется заново реализовать do в каждом подклассе.Это будет выглядеть так

struct A
{
  virtual void Do (Z & z) const
  {
    // do stuff

    use(z);

    // do more stuff
  }

  virtual void use(Z & z) const
  {
    z.use(*this);
  }
};

struct B : public A
{
  virtual void use(Z & z) const
  {
    z.use(*this);
  }
};

struct Z
{
  void use (A const & a) {}
  void use (B const & b) {}
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...