Двойная рассылка и шаблон класса

У меня есть код 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, чтобы делать то, что вы хотите, но это не будет с полиморфизмом типов.

Другие вопросы по тегам