Как применить перегруженную полиморфную функцию к элементам вектора-указателя базового класса - PullRequest
1 голос
/ 04 февраля 2020

У меня есть базовый класс Object:

struct Object{
};

и n (в данном случае 2) классов, которые наследуются от этого

struct Integer : public Object{
  int i_;
  Integer(int i) : i_{i}{}
}

struct Float : public Object{
  float f_;
  Float(float f) : f_{f}{}
}

By (ab-) используя полиморфизм, я могу теперь хранить эти два типа в векторе:

std::vector<Object*> object_list{new Integer(1), new Float(2.1), new Integer(3), new Float(4.2)};

Но теперь я хотел бы сложить все эти значения вместе.

Я могу вспомнить ...

1) ... определение функций

Integer* add(Integer* i, Integer* j);
Float*   add(Integer* i, Float* f);
Float*   add(Float* f, Float* g);
Float*   add(Float* f, Integer* i);

Но для этого потребуется динамически привести Object ко всем доступным типам - дважды, что кажется катастрофой, если у меня достаточно детских классов.

2) ... Шаблоны, но это не сработает, потому что типы не известны во время компиляции.

Итак, что является наиболее эффективным способом с учетом следующих требований:

* Время выполнения более важно, чем использование памяти (хотя оно должно выполняться в системе 8 ГБ)

* Он должен поддерживать произвольное количество дочерних классов, но должен быть не менее 20

* Не ограничивается добавлением, но должна поддерживаться произвольная функция f(Object* a, Object* b)

* Дизайн классов еще не установлен. Если что-то работает, что требует изменения (или изменения общей структуры в себе), которое возможно

* Все возможные типы известны заранее, внешние библиотеки DLL не должны поддерживаться

* Не необходимо поддерживать множественное наследование

* Не обязательно быть устойчивым в обработке ошибок. Восстановить было бы неплохо, но я могу жить с SEGFAULT.

Ответы [ 2 ]

3 голосов
/ 04 февраля 2020
using Object = std::variant<Float, Integer>;

теперь вы можете иметь std::vector<Object> и хранить в нем Float с и Integer с.

struct Integer {
  int val = 0;
  friend std::ostream& operator<<( std::ostream& os, Integer const& obj ) {
    return os << obj.val;
  }        
};
struct Float {
  double val = 0.;
  friend std::ostream& operator<<( std::ostream& os, Float const& obj ) {
    return os << obj.val;
  }        
};

using Object = std::variant<Integer, Float>;
std::ostream& operator<<( std::ostream& os, Object const& obj ) {
  // note: if the type in Object doesn't have a << overload,
  // this will recurse and segfault.
  std::visit( [&]( auto const& e ){ os << e; }, obj );
  return os;
}

Integer add_impl(Integer const& i, Integer const& j) { return {i.val + j.val}; }
Float   add_impl(Integer const& i, Float const& j) { return {i.val + j.val}; }
Float   add_impl(Float const& i, Float const& j) { return {i.val + j.val}; }
Float   add_impl(Float const& i, Integer const& j) { return {i.val + j.val}; }

Object  add( Object const& lhs, Object const& rhs ) {
  return std::visit( []( auto& lhs, auto& rhs )->Object { return {add_impl( lhs, rhs )}; }, lhs, rhs );
}

Код теста:

Object a = Integer{7};
Object b = Float{3.14};
Object c = Integer{-100};
Object d = Float{0.0};

std::cout << add( a, b ) << "," << add( b, c ) << "," << add( c, d ) << "," << add( add(a, b), add( c, d ) ) << "\n";

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

Тип возвращаемого значения - Object, но он будет содержать либо Float или Integer во время выполнения.

Список поддерживаемых вами типов должен находиться в одном месте по определению Object. Эти объекты не обязательно должны быть связанными типами.

Вы можете расширить add_impl в пространстве имен типов в Object вместо центрального расположения. ADL будет использоваться для поиска набора перегрузки.

Конечно, Я бы реализовал operator+ вместо add.

Есть несколько приемов, которые вы можете использовать используйте, чтобы исправить:

// note: if the type in Object doesn't have a << overload,
// this will recurse and segfault.

эту проблему; в основном что-то вроде:

namespace ObjectOnly {
  struct Object;
  struct Object:std::variant<Integer, Float> {
    using std::variant<Integer, Float>::variant;
    std::variant<Integer, Float> const& base() const& { return *this; }
    std::variant<Integer, Float> & base()& { return *this; }
    std::variant<Integer, Float> const&& base() const&& { return std::move(*this); }
    std::variant<Integer, Float> && base()&& { return std::move(*this); }
  };
  Object add_impl( Object const& lhs, Object const& rhs ) {
    return std::visit( [](auto& lhs, auto& rhs)->Object { return {lhs+rhs}; }, lhs.base(), rhs.base() );
  }
  Object operator+( Object const& lhs, Object const& rhs ) {
    return add_impl( lhs, rhs );
  }
  std::ostream& stream_impl( std::ostream& os, Object const& obj ) {
    std::visit( [&]( auto const& e ){ os << e; }, obj.base() );
    return os;
  }
  std::ostream& operator<<( std::ostream& os, Object const& obj ) {
    return stream_impl( os, obj );
  }
}

это заблокирует add_impl от возможности видеть ObjectOnly::operator+. Он по-прежнему сможет видеть operator+ в том же пространстве имен, что и Float или Integer.

См. здесь . Если вы отредактируете Integer, чтобы не поддерживать <<, вы получите время компиляции вместо ошибки времени выполнения.

1 голос
/ 04 февраля 2020

Если вы можете выбрать один тип в качестве канонического «общего» типа и обеспечить преобразование типов полиморфных c в этот общий тип, то вы можете использовать его в качестве конечного и промежуточного результата суммы.

Для ваших примеров классов можно использовать объект float для представления их значения:

struct Object{
    operator float() = 0;
};

Затем вы можете вычислить сумму с помощью al oop:

float sum = 0;
for (Object* o : object_list) {
    sum += *o;
}
...