Как различать объекты в полиморфном списке?

Пусть есть какой-нибудь интерфейс, например геометрической фигуры, и у него есть 3 наследника: квадрат, треугольник, 5-угольник. Пусть указатели на них хранятся в одном векторе указателей. Возникает задача, как раскрасить все квадраты в красный цвет, если мы не можем отличить тип фигуры через полиморфный указатель. Я знаю 2 решеня:

  1. Динамик-каст, что является каким-то костылём
  2. Добавить поле вид внутрь объекта, что тоже сомнительно.

Как тогда покрасить в красный цвет только квадраты? Как с такой проблемой работать в общем случае?


Ответы (3 шт):

Автор решения: Kromster

Никак, полиморфный список он не для этого :-)

В общем случае, тут может сработать принцип Tell, Dont Ask. Когда мы не спрашиваем у каждого объекта какой он и не говорим ему по результатам его ответа что ему делать, а сразу помещаем эту логику в объект, чтобы он сам решал, удовлетворяет ли он условиям и имеет ли нужные возможности.

введите сюда описание изображения

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

→ Ссылка
Автор решения: HolyBlackCat
  1. Динамик-каст, что является каким-то костылём
  2. Добавить поле вид внутрь объекта, что тоже сомнительно.

Второе - ничем не лучше чем первое (возможно второе быстрее; если вам это важно - профилируйте). В первом нет ничего плохого, иногда это лучшее решение.


Есть три решения, каждое для своих случаев:

  1. Виртуальная функция.
  2. dynamic_cast
    (или сравнение typeid, если нужен конкретный класс а не его потомки)
    (или поле с указанием типа класса, если это в вашем случае чем-то лучше)
  3. Рядом с объектом хранить указатель на функцию, которая делает то что нужно.

1 - Вариант по умолчанию.

2,3 - Если функции не место в родителе. Например, если классы используются во всем приложении, а такая раскраска нужна в одном месте. Тогда странно тащить ее в общего родителя. (Либо, если родитель - библиотечный, и его нельзя менять.)

3 удобнее, если есть где хранить эту функцию (особенно если объект создаете вы сами - тогда можно и правильную функцию подложить сразу, не изучая тип dynamic_castами), а 2 - если объекты хранятся где-то снаружи, и никакие метаданные к ним не подложить.


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

→ Ссылка
Автор решения: isnullxbh

Определяете базовый класс для "заполнителей":

struct Filler
{
    virtual ~Filler() = default;
    virtual void operator()(Square&) const {}
    virtual void operator()(Triangle&) const {}
    // ...
};

В базовый класс для фигур добавляете чисто виртуальную функцию fill:

struct Shape
{
    virtual ~Shape() = default;
    // ...
    virtual void fill(const Filler&) = 0;
};

В наследниках Shape переопределяете эту функцию:

struct Square : Shape
{
    void fill(const Filler& filler)
    {
        filler(*this);
    }
};

Определяете "заполнитель" только для квадратов:

struct SquareRedFiller
{
    void operator()(Square& s){ /* code */ }
};

Кажется, все :) В SquareRedFiller мы переопределили только оператор для квадратов, все остальные - реализация по умолчанию (которая ничего не делает).

→ Ссылка