C ++ шаблонизированный интерфейс - PullRequest
1 голос
/ 14 июля 2009

Это лучше всего описано в псевдокоде:

class Thing {};

interface ThingGetter<T extends Thing> {
    T getThing();
}

class Product extends Thing {};

class ProductGetter<Product> {
    Product getThing() {
        // Some product code
    }
}

class SpecialProductGetter extends ProductGetter {
    Product getThing() {
        p = ProductGetter::getThing();
        // Do some special stuff;
        return p;
    }
}

class NeatProductGetter extends ProductGetter {
    Product getThing() {
        p = ProductGetter::getThing();
        // Do some neat stuff;
        return p;
    }
}

У меня будут и другие "вещи" с другими добытчиками.

Я пытался кодифицировать это в C ++, но это не нравится:

template <> class ThingGetter <Product> {

ни:

class NeatProductGetter : public ThingGetter <Product> {
  1. Можете ли вы смоделировать это в C ++, не заставляя getThing возвращать Thing, а затем приводить его как сумасшедший?
  2. Если так, то как? Если нет, каков наилучший способ сделать это?

Спасибо!

Ответы [ 2 ]

1 голос
/ 14 июля 2009

Поскольку вопрос задается кем-то, имеющим опыт работы с Java, я буду толковать его в терминах Java. Вы хотите определить универсальный интерфейс, который будет возвращать объект, полученный из T:

template<typename T>
struct getter
{
   virtual T* get() = 0; // just for the signature, no implementation
};

Обратите внимание на изменения: функция объявлена ​​виртуальной, так что она будет вести себя полиморфно в производных объектах. Возвращаемое значение - указатель вместо объекта. Если вы сохраняете объект в своем интерфейсе, компилятор нарезает (обрезает неосновную часть возвращаемого объекта) возвращаемое значение. С некоторой магией метапрограммирования (или прибегая к форсированию) вы можете заставить компилятор проверить ограничение, которое T наследует от данного типа.

Теперь вопрос в том, почему вы хотели бы сделать это ... Если вы определяете неуниверсальный интерфейс (абстрактный класс), который возвращает Thing по указателю, вы можете просто получить ту же семантику проще:

struct Thing {};
struct AnotherThing : public Thing {};
struct getter
{
   virtual Thing* get() = 0;
};
struct AnotherGetter : public getter
{
   virtual AnotherThing* get() { return 0; }
};
struct Error : public getter
{
    int get() { return 0; } // error conflicting return type for get()
};
struct AnotherError : public getter
{
};
int main() {
   AnotherError e; // error, AnotherError is abstract (get is still pure virtual at this level)
}

Компилятору потребуются все экземпляры классов, производные от getter, для реализации метода get (), который возвращает указатель Thing by (или ковариантный тип возврата: указатель на класс, производный от Thing). Если производный класс пытается вернуть другой тип, компилятор пометит его как ошибку.

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

Обратите внимание, что если вы используете разные методы получения на самом производном уровне в иерархии, каждый метод get() возвращает наиболее производный элемент из иерархии Thing, в этот момент вам вообще не нужно будет понижать число. Только если вы используете разные геттеры через базовый интерфейс getter, вы получите один и тот же тип возврата для всех (Thing*)

Важно отметить, что шаблоны полностью разрешаются во время компиляции. Это означает, что попытка использовать шаблоны для решения необходимости (действительно ли вам это нужно?) Для даункаста не поможет вам, если вы используете различные геттеры через интерфейс (ссылка / указатели на базовый геттер).

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

1 голос
/ 14 июля 2009

В C ++ это будет выглядеть так: (Я изменил "class" на "struct", чтобы избежать кучки объявлений "public:". Однако это одно и то же.)

class Thing { };

template <typename T>
struct ThingGetter {
    T getThing();
};

struct Product : public Thing  { };

struct ProductGetter : public ThingGetter<Product>{
    Product getThing() { return Product(); }
};

struct SpecialProductGetter : public ProductGetter {
    Product getThing() {
        Product p = this->ProductGetter::getThing();
        return p;
    }
};

struct NeatProductGetter : public ProductGetter {
    Product getThing() {
        Product p = ProductGetter::getThing();
        return p;
    }
};

...

SpecialProductGetter spg;
spg.getThing();
NeatProductGetter npg;
npg.getThing();

Другим решением является использование специализации шаблона:

struct Thing { };
struct Widget { };

template <typename T>
struct Getter {
    T getIt() = 0;
};

template <>
struct Getter<Thing> {
    Thing getIt() { return Thing(); }
};

template <>
struct Getter<Widget> {
    Widget getIt() { return Widget(); }
};
typedef Getter<Widget> WidgetGetter;

struct SpecialWidgetGetter : public WidgetGetter {
    Widget getIt() { return this->WidgetGetter::getIt(); }
};

struct FizzledWidgetGetter : public WidgetGetter {
    Widget getFizzledWidget() { return getIt().fizzle(); }
};


...

Getter<Thing> tg;
tg.getIt();
WidgetGetter wg;
wg.getIt();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...