Двойная рассылка и шаблон класса
У меня есть код C++, где я сравниваю другой класс, производный от общего материнского класса, Foo
, Если два класса имеют не один и тот же тип, сравнение всегда false
, В противном случае он сравнивает некоторые внутренние данные, специфичные для данного класса.
Мой код выглядит так:
class Bar;
class Baz;
class Foo
{
public:
virtual bool isSame( Foo* ) = 0;
virtual bool isSameSpecific( Bar* ){ return false; }
virtual bool isSameSpecific( Baz* ){ return false; }
};
class Bar : public Foo
{
public:
bool isSame( Foo* foo){ return foo->isSameSpecific(this); }
bool isSameSpecific( Bar* bar){ return bar->identifier == identifier; }
int identifier;
};
// and the same for Baz...
Это прекрасно работает (я думаю, что это двойная отправка), я могу сравнить Bar
а также Baz
только с указателями на Foo
,
Но теперь приходит проблема. Я должен добавить шаблон класса:
template< typename T>
class Qux : public Foo
{
//...
};
Проблема в том, что в Foo
Я не могу объявить метод isSameSpecific
за Qux*
потому что это будет виртуально и шаблонно.
Вопрос: есть ли изящный способ преодолеть эту проблему?
4 ответа
На самом деле нет решения этой проблемы: вам нужен isSameSpecific
функция для каждого экземпляра шаблона, который вы используете. (Другими словами, в Foo
:
template <typename T>
virtual bool isSameSpecific( Qux<T>* );
незаконно, но:
virtual bool isSameSpecific( Qux<int>* );
virtual bool isSameSpecific( Qux<double>* );
// etc.
нет.)
Возможно, вам удастся избежать создания QuxBase
и имея Qux<T>
вытекают из этого. Скорее всего, это просто переместит проблему в QuxBase
, но еслиisSameSpecific
не зависит от типа T
Например, поскольку вы можете определить некоторый канонический охватывающий тип, это может быть выполнимо. Не зная больше о Qux
а такжеisSameSpecific
Сложно сказать. (ЕслиQux<T>::isSameSpecific
должен всегда возвращаться false
например, если типы экземпляров отличаются, вы можете ввести QuxBase::isSameSpecific
и перешлите к другой виртуальной функции, если типы идентичны.)
Обратите внимание, что подобные проблемы влияют на все альтернативные способы реализации множественной диспетчеризации. В конце концов, вы запрашиваете диспетчеризацию для открытого набора типов, что означает потенциально бесконечное количество различных функций.
РЕДАКТИРОВАТЬ:
Просто чтобы быть ясно: я предполагаю, что ваш isSame
это просто пример, и что фактические операции могут быть более сложными. Фактический код, который вы показываете, явно соответствует тому, что я предлагаю во втором абзаце; на самом деле, это может быть реализовано даже без многократной отправки. Просто определите канонический тип "идентификатор", определите виртуальный getCanonicalIdentifier
функция, и использовать это в isSame
:
bool Foo::isSame( Foo const* other ) const
{
return getCanonicalIdentifier()
== other->getCanonicalIdentifier();
}
В этом отношении, если различные типы подразумевают, что isSame
возвращает false (часто бывает, если isSame
означает то, на что это похоже), все, что вам также не нужно двойной отправки:
bool Foo::isSame( Foo const* other ) const
{
return typeid( *this ) == typeid( *other )
&& isSameSpecific( other );
}
Производная isSameSpecific
придется преобразовать тип указателя, но так как они гарантированно совпадают с типом указателя this
это простая и безопасная операция.
И наконец: если классы не имеют семантики значений (и почти наверняка не должно быть, если задействован полиморфизм), то что-то простое:
bool Foo::isSame( Foo const* other ) const
{
return this == other;
}
может хватить.
Все это относится только к чему-то вроде isSame
, тем не мение. Если у вас есть другие функции, которые затрагиваются, вы вернетесь к тому, что я изначально сказал.
Как насчет использования RTTI:
#include <typeinfo>
struct Foo
{
virtual ~Foo() { }
virtual bool compare_with_same(Foo const & rhs) = 0;
};
struct Bar : Foo
{
int thing;
virtual bool compare_with_same(Foo const & rhs)
{
assert(dynamic_cast<Bar const *>(&rhs) != nullptr);
return static_cast<Bar const &>(rhs).thing == thing;
}
}
bool operator==(Foo const & lhs Foo const & rhs)
{
return typeid(lhs) == typeid(rhs) && lhs.compare_with_same(rhs);
}
В качестве альтернативы вы можете положить typeid
код в каждом compare_with_same
переопределения. Это может быть немного безопаснее.
Вы правы, что это двойная отправка, и вы правы, что, к сожалению, метод не может быть одновременно virtual
а также template
(последний является вопросом реализации).
Я боюсь, что нет возможности сделать это с чистым дизайном; Однако вы можете обмануть в Qux
,
template <typename T>
class Qux: public Foo {
virtual bool isSame( Foo* foo ) {
if (Qux* q = dynamic_cast<Qux*>(foo)) {
return *this == *q;
}
return false;
}
}; // class Qux
Конечно, dynamic_cast
немного обманывает (как и все броски к детям), но эй, это работает!
Нота Бене: isSame
методы должны быть наверное const
и возьми const
аргументы, ака virtual bool isSame(Foo const* foo) const;
Компилятор должен знать (конечный) набор isSameSpecific
виртуалы в то время, когда он анализирует class Foo
определение. Все виртуалы имеют зарезервированные записи в виртуальной таблице. Шаблон Qux
может быть отменено неограниченное количество раз, что требует неограниченного количества виртуальных Foo
, Понятно, что это не может работать, даже не пытаясь описать метод определения их всех.
Вы, вероятно, можете использовать typeinfo, чтобы делать то, что вы хотите, но это не будет с полиморфизмом типов.