C ++ - & CRTP.Тип стирание против полиморфизма - PullRequest
8 голосов
/ 30 ноября 2010

ОК, пошли.Я пытаюсь использовать шаблон CRTP , чтобы исключить необходимость полиморфизма в моем приложении.Я использую подход, подобный приведенному ниже

template <RealType> class Base 
{

    void doSomething()
    {
         static_cast<RealType>(this)->doSomethingImpl()
    }

class Derived1 : public Base
{
    void doSomethingImpl()
    {
        /* do something, for real */
    }
}

class Derived2 : public Base
{
    void doSomethingImpl()
    {
        /* do something else */
    }
}

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

Теперь представьте, что я хочу сохранить все мои классы Derived # в контейнере.Скажем, вектор.

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

Однако мне кажется, что если я захочу это сделать, мне придется сделать doSomething виртуальным в SuperBase.И моя цель в основном не иметь vtable.

Второй подход: я использую стирание типов, то есть что-то вроде boost :: any, для хранения своих элементов в векторе.Но тогда я не вижу способа, которым я могу перебирать элементы и вызывать для них doSomething, потому что для «использования» boost :: any мне нужно знать реальный тип объекта при итерации.

Как вы думаете, что я хочу сделать, даже возможно?

Мне кажется, это потому, что doSomething () является частью Base, но кроме использования наследования я не могу найти способ сделать это ....

Ответы [ 4 ]

11 голосов
/ 30 ноября 2010

И моя цель в основном не в том, чтобы иметь vtable.

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

Я не могу не спросить: Почему вы хотите избежать vtables? (И если вы так решительно настроены, почему вы не программируете на C?)

7 голосов
/ 30 ноября 2010

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

Объединения более популярны в C, например, сообщения о событиях в X-Windows основаны на объединениях, потому что они разбиты в C ++.

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

ООП не может обеспечить объединение: оно предоставляет подтипы.

Шаблоны предоставляютопять нечто совершенно иное: параметрический полиморфизм.

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

Итак, теперь ясно, что в вашем массиве все, что вам нужно поместить, это объединение всех ваших классов, и ваша проблема исчезнет.

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

Что-то вроде:

enum tag {tag_X, tag_Y};

struct X { int x; };
void px(X a) { cout << a.x; }
struct PX { tag t; X a; void (*p)(X); };

struct Y { double x; };
void py(Y a) { cout << a.x; };
struct PY {tag t; Y a; void (*p)(Y); };

union XY { PX anX; PY anY; };

PX x1 = { tag_X, { 1 }, px };
PY y1 = { tag_Y, { 1.0 }, py };

XY xy1.anPX = x1;
XY xy2.anPy = x2;

XY xys[2] = {xy1, xy1};

xy = xys[0];
switch (xy.anPX.tag) { // hack
  case tag_X: xy.anPX.p (xy.PX.x); break;
  case tag_Y: xy.anPY.p (xy.PY.x); break;
}

Если вы считаете, что это некрасиво, вы правы: C и C ++ умерли.Другое решение состоит в том, чтобы использовать тег и указатель, который приведен к void *, а затем использовать тег для приведения к требуемому типу: это намного проще, но требует выделения данных в куче, и, следовательно, у вас есть проблема с управлением памятью.Другой альтернативой является вариант типа Boost (который автоматизирует некоторые операции по уборке, но все еще очень уродлив).

Вот аналогичный код в Ocaml:

type xy = X of int | Y of double
let print u =
  match u with 
  | X x -> print_int x 
  | Y x -> print_double x
in 
  print (X 1);
  print (Y 2.0)

В этом коде X и Y - этотеги кода C выше, они называются конструкторами типов, потому что они создают тип xy из целого или двойного (соответственно).В выражении соответствия есть просто переключатель с автоматическим выбором правильного типа компонента и областью видимости, который используется для гарантии того, что вы не можете ссылаться на неправильный компонент (как вы могли бы в коде C), также нет прерывания, обработчики соответствия неDrop Thru, и управление памятью осуществляется сборщиком мусора.

2 голосов
/ 04 августа 2014

Вы можете объединить обе силы.

#include <iostream>

struct AnimalBase
{
    virtual std::string speak() const = 0;
    virtual ~AnimalBase() {};
};

template <typename Derived>
struct Animal : AnimalBase
{
    std::string speak() const
    {
        return static_cast<const Derived*>(this)->speak();
    }
};

struct Dog : Animal<Dog>
{
    std::string speak() const
    {
        return "Woof! Woof!";
    }
};

struct Cat : Animal<Cat>
{
    std::string speak() const
    {
        return "Meow. Meow.";
    }
};

int main()
{
    AnimalBase* pets[] = { new Dog, new Cat };

    std::cout << Dog().speak() << '\n'
              << Cat().speak() << '\n'
              << pets[0]->speak() << '\n'
              << pets[1]->speak() << std::endl;

    delete pets[0];
    delete pets[1];
    return 0;
}
2 голосов
/ 30 ноября 2010

Будет трудно (в лучшем случае, взломать) держать эти объекты в контейнере.Вы разработали дизайн полиморфизма, и все же, похоже, вы действительно хотите его использовать, чтобы вы могли держать объекты как container<mybaseclass> и использовать их полиморфно.

Из вашей публикации мне не понятно, почему вы хотите избежать vtable.Если это для производительности, вы, вероятно, чрезмерной оптимизации.Без дополнительной информации о том, почему вы идете по этому маршруту, трудно что-либо порекомендовать, кроме как «использовать базовый класс».

...