Полиморфизм С++ объекта в массиве

Я разработчик встроенного программного обеспечения и из мира бит и C. В этом мире есть данные во флэш-памяти, представленные const в C. И есть данные в ОЗУ. ОЗУ стоят дорого и ограничены, а флеш-память дешевая и достаточно. Кроме того, динамическое распределение памяти с использованием new, delete, malloc и т.д. Не допускается из-за проблемы фрагментации или правил безопасности, предпочтительны статические конструкции.

У меня около 2000 объектов, которые имеют одинаковые постоянные свойства, но отличаются поведением. Поэтому для них я определил Shape Class как базовый класс, который содержит общие свойства моих объектов. И для представления различного поведения Shape Class имеет один абстрактный метод под названием Print(), который будет перезаписан родителями.

ShapeList - важная часть. Это массив констант, состоящий из "const Shapes", так что они будут помещены в секцию флэш-памяти компоновщиком.

Ниже программа выводит результат:

I'm a Shape has 3 dots
I'm a Shape has 4 dots
I'm a Shape has 5 dots

Ожидаемый результат:

I'm a Triangle has 3 dots
I'm a Rectangle has 4 dots
I'm a Pentagon has 5 dots

Мне нужно полиморфное поведение. Когда я печатаю Треугольник, он должен вести себя как Треугольник, а не как Форма. Как я могу это сделать?

Спасибо.

#include <array>
#include <cstdio>
class Shape
{
    public:
    const int DotCount;
    Shape(const int dot): DotCount(dot) {}
    virtual void Print(void) const; // this is virtual method
};

void Shape::Print(void) const
{
    printf("I'm a Shape has %d dots\n", DotCount);
}

class Triangle: public Shape
{
    public:
    Triangle(void): Shape(3) { }
    void Print(void) const;
};

void Triangle::Print(void) const
{
    printf("I'm a Triangle has %d dots\n", DotCount);
}

class Rectangle: public Shape
{
    public:
    Rectangle(void): Shape(4) { }
    void Print(void) const;
};

void Rectangle::Print(void) const
{
    printf("I'm a Rectangle has %d dots\n", DotCount);
}

class Pentagon: public Shape
{
    public:
    Pentagon(void): Shape(5) { }
    void Print(void) const;
};

void Pentagon::Print(void) const
{
    printf("I'm a Pentagon has %d dots\n", DotCount);
}

const std::array<const Shape, 3> ShapeList = { Triangle(), Rectangle(), Pentagon() };

int main(void)
{
    ShapeList.at(0).Print();
    ShapeList.at(1).Print();
    ShapeList.at(2).Print();
    return(0);
}

Больше проблем: Сегодня я понял, что есть еще одна проблема с виртуальными функциями. Когда я добавляю какие-либо виртуальные функции в базовый класс, компилятор начинает игнорировать директиву "const" и автоматически помещает объект в ОЗУ вместо флэш-памяти. Я не знаю почему. Я задал этот вопрос в IAR. Вывод, который я получил до сих пор, заключается в том, что полиморфное поведение невозможно с ROMable классами даже с кучей:/

Ответ 1

Как указывали другие, удобный и распространенный способ не работает. Исправление этого результата приводит к коду, который противоречит ограничениям вашей целевой платформы. Тем не менее, вы можете эмулировать полиморфизм по-разному несколькими способами.

Вы можете выделить объекты по типу следующим образом:

const Triangle tris[] = {tri1, tri2, ...};
const Rectangle rects[] = {rect1, rect2, ...};
// for correct order, if needed
const Shape * const shapes[] = {&tris[0], &rects[2], &rects[0], ...}:

Вам все равно нужно сделать все методы (которые ведут себя по-разному для разных типов) virtual, и вы платите дополнительный указатель (два, если вы считаете указатель vtable, что было бы немного несправедливо) на объект. Вы также можете удалить все virtual в пользу явного тега:

enum ShapeKind { Triangle, Rectangle, Pentagon };
struct Shape {
    ShapeKind kind;
    int sides;
    ...
};

Используйте union, если различным подклассам нужны очень разные данные элемента. Это имеет множество серьезных ограничений и приводит к довольно уродливому коду, но может работать хорошо. Например, вам нужно знать свою иерархию спереди, а подклассы должны быть примерно одного размера. Обратите внимание, что это не обязательно быстрее, чем альтернатива virtual, но когда она применима, она может занимать меньше места (байт вместо указателя vtable), а сделать непрозрачность более сложной.

Ответ 2

В этой версии не используется динамическая память:

Triangle tri;
Rectangle rect;
Pentagon pent;
const std::array<const Shape*, 3> ShapeList {
    &tri, &rect, &pent
};
for (unsigned int i = 0; i < ShapeList.size(); i++)
    ShapeList[i]->Print();

В таких языках, как С#, вы можете использовать ключевое слово as для достижения "полиморфизма". В С++ он выглядит примерно так:

    const Triangle* tri = dynamic_cast<const Triangle*>(ShapeList[i]);
    if (tri)
        static_cast<Triangle>(*tri).SomethingSpecial();

Если указатель, возвращаемый dynamic_cast, действителен, вы можете вызвать специальную функцию Triangle. Это, например, позволит вам иметь цикл, который выполняет итерацию более ShapeList и вызывает только методы Triangle. Если вы можете использовать исключения, подумайте об упаковке в блок try catch и ловите std::bad_cast.

Примечание: вам нужен указатель const, потому что ShapeList[i] является константой. Причина, по которой требуется static_cast, заключается в том, что вы вызываете метод non-const для указателя const. Вы можете добавить спецификатор const, например SomethingSpecial() const, а затем просто tri->SomethingSpecial(). В противном случае вы просто отключите const.

Например:

static_cast<Triangle*>(tri)->SomethingSpecial();
// error: static_cast from type 'const Triangle*' to type 'Triangle*' 
// casts away qualifiers

Это будет работать:

const_cast<Triangle*>(tri)->SomethingSpecial();

Ответ 3

Вы можете использовать полиморфизм вместе со всеми вашими ограничениями с незначительным изменением кода:

const Triangle triangle;
const Rectangle rectangle;
const Pentagon pentagon;

const std::array<const Shape*, 3> ShapeList = { &triangle, &rectangle, &pentagon };

Ответ 4

Любое легкое исправление заключается в том, чтобы добавить строку в форму, определяющую тип формы.

class Shape
{
    public:
    const int DotCount;
    const char* shapeType
    Shape(const int dot, const char* type): DotCount(dot), shapeType(type) {}
    void Print(void) const;
};

void Shape::Print(void) const
{
    printf("I'm a "); printf(shapeType); printf(" has %d dots\n", DotCount);
}

class Triangle: public Shape
{
    public:
    Triangle(void): Shape(3, "Triangle") { }
};

Ответ 5

Другим решением, которое я нашел в C для полиморфной динамической отправки без динамического распределения, является передача указателей vtable вручную, таких как GHCs desugaring в классах в Haskell. Такой подход также был бы разумным в С++, потому что его легкий и строго более общий, чем позволяет объектная система С++.

Перегруженная/полиморфная функция принимает указатель на структуру указателей функций для каждого класса типов, к которому принадлежит тип параметров - сравнение равенства, упорядочение и c. Таким образом, вы можете:

template<class Container, class Element>
struct Index {
  size_t (*size)(const Container& self);
  const Element& (*at)(const Container& self, size_t index);
};

enum Ordering { LT, EQ, GT };

template<class T>
struct Ord {
  Ordering (*compare)(const T& a, const T& b);
};

template<class Container, class Element>
const Element* maximum(
  const Index<Container, Element>& index,
  const Ord<Element>& ord,
  const Container& container) {

  const size_t size = index.size(container);
  const Element* max = nullptr;

  for (size_t i = 0; i < size; ++i) {
    const Element& current = index.at(container, i);
    if (!max || ord.compare(current, *max) == GT)
      max = &current;
  }

  return max;

}

Поскольку параметры типа "phantom типы" не используются репрезентативно, компоновщик должен иметь возможность дедуплицировать такую ​​функцию, если вы беспокоитесь о размере кода. Тип-небезопасная, но, возможно, более удобная для компилятора альтернатива - использовать void*.

В С++ вы также можете передавать функции vtable в качестве параметров шаблона, если вы знаете их во время компиляции, то есть ручную девиртуализацию. Это допускает больше оптимизаций (например, inlining), но, очевидно, не позволяет динамическую отправку.

Одно предостережение: поскольку у вас нет частичного приложения или закрытия приложений, вы найдете интересный опыт для частичной специализации, такой как Haskell:

instance (Ord a) => Ord [a] where ...

Что говорит, что список вещей [a] имеет упорядочение, если элементы a имеют порядок.