Шаблонное возвращаемое значение C с чисто виртуальной функцией

#c #templates #virtual #abstract-class

#c #шаблоны #виртуальный #абстрактный класс

Вопрос:

У меня есть абстрактный класс Handle<T>, который содержит ссылки на объекты типа T. Я хочу, чтобы этот класс мог быть преобразован в Handle<U> , где U — суперкласс T. Я бы использовал наследование, но здесь это не работает. Как бы я это сделал? Какие есть хорошие альтернативы?

Пример псевдокода:

 template<class T>
class Handle {
public:
    virtual ~Handle () {}
    virtual T amp; operator* () const = 0;
    virtual T * operator-> () const = 0;
    virtual template<class U> operator Handle<U>* () const = 0; // being lazy with dumb pointer
};

template<class T>
class ConcreteHandle : public Handle<T> {
public:
    explicit template<class U> ConcreteHandle (U * obj) : obj(obj) {}
    virtual ~ConcreteHandle () {}
    virtual T amp; operator* () const {
        return *obj;
    }
    virtual T * operator-> () const {
        return obj;
    }
    virtual template<class U> operator Handle<U>* () {
        return new ConcreteHandle<U>(obj);
    }
private:
    T * obj;
};
  

Как и было запрошено, это то, что я делаю

 class GcPool {
public:
    virtual void gc () = 0;
    virtual Handle<GcObject> * construct (GcClass clazz) = 0;
};

class CompactingPool : public GcPool {
public:
    virtual void gc () { ... }
    virtual Handle<GcObject> * construct (GcClass clazz) { ... }
private:
    Handle<GcList<Handle<GcObject> > > rootSet; // this will grow in the CompactingPool's own pool
    Handle<GcList<Handle<GcObject> > > knownHandles; // this will grow in the CompactingPool's own pool.
};
  

knownHandles должен быть совместим с Handle, чтобы он мог находиться в корневом наборе CompatingPool. То же самое касается rootSet. Я загружу эти специальные дескрипторы, чтобы не возникло проблемы с курицей и яйцом.

Комментарии:

1. Почему бы просто не создать operator Handle<U>() concrete в базовом классе? Действительно ли когда-либо имело смысл предоставлять другую реализацию?

2. Моему приложению нужны (читай: хочет) различные типы дескрипторов. Разные типы пулов памяти (gc) выдают разные дескрипторы. Опять же, я мог бы переусердствовать. Тем не менее, это все еще интересный вопрос для меня.

3. @Tony: Это заставило бы class Handle использовать его производный класс в своей реализации. Вы могли бы это сделать, но предполагая, что на самом деле существует несколько возможных классов, производных от class Handle , тогда это не имеет смысла. Если их нет, то для начала не должно быть полиморфизма.

4. @trinithis: Рассматривали ли вы возможность использования аргумента шаблона вместо полиморфизма для управления распределителем? Обратите внимание, что это не обязательно должно быть таким сложным, как распределители STL.

5. Я пишу компилятор и хочу выполнить сборку мусора на этом языке. У меня есть GcPool как абстрактный класс. GcPool имеет чисто виртуальную функцию построения (класса), которая возвращает дескриптор<GcObject> . Я обновляю свою первую реализацию GcPool, CompactingPool, для внутреннего использования дескрипторов после небольшой начальной загрузки, чтобы он мог выделять больше дескрипторов в своем собственном пуле. Но я хочу, чтобы эти дескрипторы имели лучшие типы, чем GcObject, такие как GcStack. Возможно, это относится к явным приведениям….

Ответ №1:

 virtual template<class U> operator Handle<U>* () const  =0;
  

Виртуальная функция шаблона не разрешена спецификацией языка.

Рассмотрите этот код в ideone, а затем увидите ошибку компиляции:

ошибка: шаблоны могут быть не ‘виртуальными’


Что теперь вы можете сделать? Одно из решений заключается в следующем:

 template<class T>
class Handle {
public:

    typedef typename T::super super; //U = super, which is a superclass of T.

    virtual ~Handle () {}
    virtual T amp; operator* () const = 0;
    virtual T * operator-> () const = 0;

    //not a template now, but still virtual
    virtual super operator Handle<super> () const = 0;  
};
  

То есть определите typedef базовый класс в производном классе и используйте его в Handle . Что-то вроде этого:

 struct Base {//...};

struct Derived : Base { typedef Base super; //...};

Handle<Derived>  handle; 
  

Или вы можете определить признаки, как:

 struct Base {//... };

struct Derived : Base { //... };

template<typename T> struct super_traits;

struct super_traits<Derived>
{
   typedef Base super;
};

template<class T>
class Handle {
public:

    typedef typename super_traits<T>::super super; //note this now!

    virtual ~Handle () {}
    virtual T amp; operator* () const = 0;
    virtual T * operator-> () const = 0;

    //not a template now, but still virtual
    virtual super operator Handle<super> () const = 0; 
};
  

На мой взгляд, super_traits это превосходное решение, поскольку вы определяете свойства производных классов без их редактирования. Кроме того, вы можете определить столько определений типов, сколько захотите; допустим, ваш производный класс имеет более одной базы, вы можете захотеть определить много определений типов или, предпочтительно, список типов.

Комментарии:

1. @Nawaz, OP хочет получить эффект от virtual template метода. Вот почему он упомянул как псевдокод . Можно предположить, что он должен знать об ошибке.

2. @iammilind: Поскольку виртуальный шаблон невозможен, то о нем сейчас не может быть и речи. Вы не можете определить виртуальный шаблон.

3. Это повторяет известную проблему … код в вопросе указывает на желаемую функциональность — Кит знает, что это не работает. То, что он хочет, это что-то функционально эквивалентное.

4.@Nawaz, Тони имеет в виду, что спрашивающий хочет некоторую функциональность, которая имитирует virtual template ,

5. @iammilind: Все зависит от определения «некоторой функциональности». Что вы под этим подразумеваете? Если вы говорите «вся функциональность» виртуального шаблона, это не является расплывчатым, но «некоторая функциональность» является расплывчатой. Кстати, я просто пытаюсь решить эту проблему. Я не понимаю, чего это не достигает.