Без наследственный полиморфизм

X: У меня есть коллекция несвязанных контейнероподобных объектов (вектор, карты, деревья, …), обрабатывающих объекты разных несвязанных типов. Время жизни объектов в этих контейнерах распределяется между некоторыми их подмножествами. У меня есть объект, отвечающий за их синхронизацию. Самая простая реализация класса синхронизации, о которой я могу подумать, — это иметь вектор BaseContainerLike указатели, где BaseContainerLike будет классом, реализующим общий интерфейс для всех контейнероподобных объектов, которыми я хочу управлять. Но не все они похожи на контейнеры. Oни мог быть использованным в качестве контейнера, но заставить их наследовать от общего базового класса кажется очень странным, и я боюсь, что это очень сильно объединит мой дизайн.

Итак, я создал ContainerLikeInterface класс как это:

struct ContainerLikeInterface {
template<T>
ContainerLikeInterface(T& t)
: create([](int i){ return t->create(i); }),    // this is just an example
delete([](int i){ return t->delete(i); }) {}

std::function<void(int)> create;
std::function<void(int)> delete;
};

template<class T>
ContainerLikeInterface make_containerLikeInterface(T& t) {
return ContainerLikeInterface(t);
}

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

  • Каковы плюсы / минусы этого подхода?
  • Это даст мне некоторые проблемы в долгосрочной перспективе?
  • Должен ли я использовать наследство вместо?
  • Должен ли я реализовать это по-другому?
  • Должен ли я использовать библиотеку вместо этого? (повышение :: TypeErasure, Adobe :: поли, или же pyrtsa / поли)

1

Решение

Ваша основная идея (не требующая наследования) хороша. Я бы рекомендовал вместо этого использовать Adobe.Poly. Когда вы используете 1 std::function на одну операцию у вас есть N виртуальных таблиц (указателей), а также потенциально N кучи (в зависимости от того, может ли SBO (Small Buffer Optimization) применяться или нет).

Вы также можете столкнуться с проблемами управления жизненным циклом объекта. В вашей реализации вы предполагаете, что реальный объект живет дольше, чем «интерфейс». Рано или поздно вы ошибетесь. Вот почему я бы посоветовал ценностно-смысловой подход. Adobe.Poly дает вам это.

С Adobe.Poly вы получаете только один vtable (указатель). Он также реализует SBO: потенциально не единственное распределение.

Я не обязательно пошел бы с Boost.TypeErasure. Это требует изучения другого «языка» для определения интерфейсов, который использует много метапрограммирования, и на сегодняшний день он не реализует SBO.

Adobe.Poly плохо документирован. Увидеть эта почта для примеров того, как вы используете это. Также см Эта бумага о том, как это реализовано.

1

Другие решения

Ваш интерфейс немного напоминает интерфейсную систему, которая сделана в
Система объектов ржавчины. Классические интерфейсы на основе VMT имеют указатель на объект, который содержит указатель на VMT. У вас есть 2 указателя: один на объект, а другой на таблицу методов. Он [почти] всегда выглядит более мощным, чем виртуальные функции с недостатками, которые вы уже упоминали (использование памяти и т. Д.)
Что касается скорости, std::function использует стандартный распределитель, чтобы сохранить указатель на t, Если вы позвоните ContainerLikeInterface конструктор много, это может привести к некоторому снижению производительности, потому что требуется по крайней мере одно распределение на std::function в вашем интерфейсе, и вы можете написать свой собственный для этого.

1

Вы по сути создаете объект прокси с ContainerLikeInterface интерфейс с указателем или ссылкой на некоторые T,

Существует способ избежать создания прокси, при этом все еще используются стандартные контейнеры, которые не могут получить ContainerLikeInterface, Это, вероятно, называется Смешанный дизайн и с идеальной пересылкой в ​​C ++ 11 это будет выглядеть так:

#include <vector>

struct IContainerLikeInterface
{
virtual void create(int) = 0;
virtual void erase(int) = 0;
};

template<class T>
struct ContainerLikeInterface : T, IContainerLikeInterface
{
template<class... Args>
ContainerLikeInterface(Args&& ...args)
: T(std::forward<Args>(args)...)
{}

// implement IContainerLikeInterface
void create(int) override;
void erase(int) override;
};

int main() {
ContainerLikeInterface<std::vector<int> > v(10);
v.size();
}

Тем не менее, это навязчиво, потому что все объявления рассматриваемых контейнеров, такие как std::vector<int>, должны быть изменены в ContainerLikeInterface<std::vector<int> >, Использование остается тем же, потому что, потому что ContainerLikeInterface<T> это T,

0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector