Двойная диспетчеризация без знания полной иерархии

Я хотел бы реализовать следующую вещь на С++:

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

Насколько мне известно, это классическое применение двойной отправки. Однако у меня есть следующее ограничение:

Должна быть возможность создавать новые классы из существующих и добавлять новые парные функции для этих новых классов без изменения существующих классов, например, во внешней библиотеке.

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

Любое предложение о том, как это реализовать? Это вообще возможно?

Обновление: в коде больше тысячи слов. Работает следующий подход:

#include <iostream>

class B;

class A
{
public:
  virtual void PostCompose(A* other)
    {
      other->PreCompose(this);
    }
  virtual void PreCompose(A* other)
    {
      std::cout << "Precomposing with an A object" << std::endl;
    }
  virtual void PreCompose(B* other);
};

class B : public A
{
public:
  using A::PreCompose;
  virtual void PostCompose(A* other)
    {
      other->PreCompose(this);
    }
  virtual void PostCompose(B* other)
    {
      other->PreCompose(this);
    }
  virtual void PreCompose(B* other)
    {
      std::cout << "Precomposing with a B object" << std::endl;
    }
};

void A::PreCompose(B* other)
  {
    PreCompose((A*)other);
  }

int main()
{
  B b;
  A* p = &b;
  p->PostCompose(p); // -> "Precomposing with a B object"
}

но при реализации A требуется знание B. Есть ли способ лучше?


person lytenyn    schedule 14.04.2011    source источник
comment
Входит ли this в пару объектов?   -  person Potatoswatter    schedule 14.04.2011
comment
Ну да, если хочешь. Я добавлю короткий пример в вопрос.   -  person lytenyn    schedule 14.04.2011


Ответы (1)


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

virtual void foo( base *argument_base ) {
    if ( derived *argument = dynamic_cast< derived * >( argument_base ) ) {
        argument->something = pair_match_foo;
    } else {
        base_class::foo( argument_base );
    }
}
person Potatoswatter    schedule 14.04.2011
comment
Проблема с примером из Википедии заключается в том, что вы должны знать обо всех производных классах при реализации базового класса, чего я не могу сделать. Я ищу метод, который позволит мне позже добавлять производные классы без изменения базового класса. - person lytenyn; 14.04.2011
comment
Чтобы сделать это более точным, я хочу двойную отправку в случае, когда у меня есть только одна иерархия классов, а не две. Не существует «целевого класса», поскольку сам класс является его целью. Это не проблема, но требует от меня знания всей иерархии классов при написании базового класса, а это не то, что мне нужно. - person lytenyn; 14.04.2011
comment
@lytenyn: Извините, я немного заржавел. Мне также не ясно, предназначена ли compose операция или сам механизм диспетчеризации… в любом случае см. обновление. - person Potatoswatter; 14.04.2011
comment
@Potatoswatter: спасибо, это аккуратно решит проблему. Однако я не хочу использовать dynamic_cast, но не могу понять, почему. Это, конечно, медленно, но в любом случае это не критичная по времени операция. Вы можете придумать что-нибудь, не связанное с dynamic_cast? Или скажите мне, что мое тревожное чувство не оправдано? - person lytenyn; 14.04.2011
comment
@lytenyn: dynamic_cast включает в себя подъем по иерархии классов, чтобы определить, является ли derived фактическим типом данного объекта или одним из его оснований. Если это фактический тип, то dynamic_cast преуспевает только после одного сравнения. В противном случае произойдет сбой после двух сравнений, потому что имеется только одна база. Если иерархия намного сложнее, то оптимизация несложная, но она должна быть достаточно быстрой. - person Potatoswatter; 14.04.2011
comment
@Potatoswatter: Хорошо, спасибо. Почему-то я все еще чувствую, что это не элегантное решение, но опять же, я не могу сказать, почему. Как-то я помню, что говорил, что если вам нужно dynamic_cast, то ваш дизайн класса, вероятно, неверен, но я не могу придумать лучшего способа решить мою проблему. - person lytenyn; 14.04.2011
comment
@lytenyn: я думаю, что это одно использование, которое не считается: v) . При желании вы можете заменить ее дополнительной виртуальной функцией, возвращающей значение enum, идентифицирующее подиерархию класса, или просто переменную-член с той же информацией. Я думаю, что этот вариант менее элегантен. Многих студентов учили использовать dynamic_cast вместо static_cast. Это большой красный флаг, и я могу представить негативную реакцию на самого оператора. - person Potatoswatter; 14.04.2011