Полиморфизм С++ объекта в массиве
Я разработчик встроенного программного обеспечения и из мира бит и 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 = ¤t;
}
return max;
}
Поскольку параметры типа "phantom типы" не используются репрезентативно, компоновщик должен иметь возможность дедуплицировать такую функцию, если вы беспокоитесь о размере кода. Тип-небезопасная, но, возможно, более удобная для компилятора альтернатива - использовать void*
.
В С++ вы также можете передавать функции vtable в качестве параметров шаблона, если вы знаете их во время компиляции, то есть ручную девиртуализацию. Это допускает больше оптимизаций (например, inlining), но, очевидно, не позволяет динамическую отправку.
Одно предостережение: поскольку у вас нет частичного приложения или закрытия приложений, вы найдете интересный опыт для частичной специализации, такой как Haskell:
instance (Ord a) => Ord [a] where ...
Что говорит, что список вещей [a]
имеет упорядочение, если элементы a
имеют порядок.