Мне интересно узнать, существует ли какой-либо жизнеспособный способ смежного хранения массива полиморфных объектов, так что методы virtual
на общей базе могут быть юридически вызваны (и отправили бы к правильному переопределенному методу в подклассе).
Например, учитывая следующие классы:
struct B {
int common;
int getCommon() { return common; }
virtual int getVirtual() const = 0;
}
struct D1 : B {
virtual int getVirtual final const { return 5 };
}
struct D2 : B {
int d2int;
virtual int getVirtual final const { return d2int };
}
Я хотел бы выделить смежный массив объектов D1 и D2 и рассматривать их как объекты B
, включая вызов getVirtual()
, который будет делегировать соответствующий метод в зависимости от типа объекта. Концептуально это кажется возможным: каждый объект знает свой тип, как правило, через встроенный указатель vtable, поэтому вы можете себе представить, сохраняя n объектов в массиве n * max(sizeof(D1), sizeof(D2))
unsigned char
и используя размещение new
и delete
для инициализации объектов и наведение указателя unsigned char
на B*
. Я уверен, что актерский состав не является законным.
Можно также представить себе создание такого объединения, как:
union Both {
D1 d1;
D2 d2;
}
а затем создав массив Both
и используя новое место для создания объектов соответствующего типа. Однако, похоже, это еще не дает возможности реально называть B::getVirtual()
безопасно. Вы не знаете последний сохраненный тип для элементов, так как вы собираетесь получить B*
? Вам нужно использовать либо &u.d1
, либо &u.d2
, но вы не знаете, какой! Существуют действительно специальные правила о "начальных общих подпоследовательностях", которые позволяют вам делать некоторые вещи в союзах, где элементы имеют общие черты, но этот применим только к стандартным типам макета. Классы с виртуальными методами не являются стандартными типами макетов.
Есть ли способ? В идеальном случае решение будет выглядеть как нерезкий std::vector<B>
, который может фактически содержать полиморфные подклассы B
. Да, если требуется, можно было бы предусмотреть, что все возможные подклассы известны заранее, но лучшее решение должно знать только максимальный вероятный размер любого подкласса (и не удается во время компиляции, если кто-то пытается добавить "слишком большой" объект).
Если это невозможно сделать с помощью встроенного механизма virtual
, другие варианты, предлагающие аналогичную функциональность, также будут интересны.
Фон
Без сомнения, кто-то спросит "почему", поэтому здесь немного мотивации:
Как правило, хорошо известно, что использование функций virtual
для реализации полиморфизма во время выполнения происходит при умеренных издержках при фактическом вызове виртуальных методов.
Не так часто обсуждается, однако, тот факт, что использование классов с виртуальными методами для реализации полиморфизма обычно подразумевает совершенно другой способ управления памятью для базовых объектов. Вы не можете просто добавлять объекты к стандартным контейнерам разных типов (но общая база): если у вас есть подклассы D1
и D2
, оба из базы B
, a std::vector<B>
будет срезать любой D1
или D2
добавлены объекты. Аналогично для массивов таких объектов.
Обычным решением является использование контейнеров или массивов указателей для базового класса, например std::vector<B*>
или, возможно, std::vector<unique_ptr<B>>
или std::vector<shared_ptr<B>>
. Как минимум, это добавляет дополнительную косвенность при доступе к каждому элементу 1 а в случае с интеллектуальными указателями он разбивает общие оптимизации контейнеров, Если вы фактически распределяете каждый объект через new
и delete
(включая косвенно), тогда затраты времени и памяти на сохранение ваших объектов просто увеличиваются на большую сумму.
Понятно, что различные подклассы общей базы могут быть сохранены последовательно (каждый объект будет потреблять столько же пространства: самого большого поддерживаемого объекта) и что указатель на объект можно рассматривать как базовый, указатель класса. В некоторых случаях это может значительно упростить и ускорить использование таких полиморфных объектов. Конечно, в общем, это, наверное, страшная идея, но для целей этого вопроса позвольте предположить, что у нее есть какое-то нишевое приложение.
1 Помимо всего прочего, это косвенное отношение в значительной степени предотвращает любую векторизация одной и той же операции, применяемой ко всем элементам, и вредит локальности ссылки с последствиями как для кэширования, так и для предварительной выборки.