Вот что я пытаюсь сделать:
Я занимаюсь разработкой кроссплатформенной IDE (Linux и Windows), которая поддерживает плагины.Мне нужно поддерживать расширяемость, используя платформу адаптера, аналогичную той, которую предоставляет Eclipse.См. здесь для получения более подробной информации, но в основном мне нужно следующее:
Позвольте Adaptee
и Adapted
быть абсолютно не связанными классами, которые уже существуют и которые нам запрещено изменять в любыхпуть.Я хочу создать класс AdapterManager
, у которого есть метод
template <class Adaptee, class Adapted> Adapted* adapt( Adaptee* object);
, который будет создавать экземпляр Adapted
с учетом экземпляра Adaptee
.Как именно создается экземпляр, зависит от функции адаптера, которую нужно зарегистрировать с помощью AdapterManager
.Каждый новый плагин должен иметь возможность предоставлять функции адаптера для произвольных типов.
Вот мои мысли о возможном решении и почему оно не работает:
CФункции RTTI ++ 11 и класс type_info
предоставляют метод hash_code()
, который возвращает уникальное целое число для каждого типа в программе.Смотрите здесь .Таким образом, AdapterManager
может просто содержать карту, которая с учетом хеш-кодов для классов Adaptee и Adapter возвращает указатель на функцию адаптера.Это делает реализацию функции adapt()
выше тривиальной:
template <class Adaptee, class Adapted> Adapted* AdapterManager::adapt( Adaptee* object)
{
AdapterMapKey mk( typeid(Adapted).hash_code(), typeid(Adaptee).hash_code());
AdapterFunction af = adapterMap.get(mk);
if (!af) return nullptr;
return (Adapted*) af(object);
}
Любой плагин может легко расширить каркас, просто вставив дополнительную карту в карту.Также обратите внимание, что любой плагин может попытаться адаптировать любой класс к любому другому классу и добиться успеха, если существует соответствующая функция адаптера, зарегистрированная в AdapterManager
, независимо от того, кто ее зарегистрировал.
- Проблема с этимэто комбинация шаблонов и плагинов (общих объектов / DLL).Поскольку два плагина могут создавать экземпляры класса шаблона с одинаковыми параметрами, это может потенциально привести к двум отдельным экземплярам соответствующих
type_info
структур и потенциально различным hash_code()
результатам, что нарушит механизм, описанный выше.Функции адаптера, зарегистрированные в одном подключаемом модуле, могут не всегда работать в другом подключаемом модуле. - В Linux динамический компоновщик, по-видимому, может работать с несколькими объявлениями типов в разных общих библиотеках при некоторых условиях в соответствии с это (пункт 4.2).Однако настоящая проблема заключается в Windows, где кажется, что каждая DLL получит свою версию экземпляра шаблона независимо от того, определена ли она также в других загруженных DLL или в основном исполняемом файле.Динамический компоновщик кажется довольно негибким по сравнению с тем, который используется в Linux.
- Я рассмотрел использование явных экземпляров шаблонов, которые, кажется, уменьшают проблему, но все же не решают ее, поскольку два разных плагина могут по-прежнему создавать экземплярытаким же образом.
Вопросы:
- Кто-нибудь знает, как добиться этого в Windows?Если бы вам было позволено изменять существующие классы, это помогло бы?
- Известен ли вам другой подход для достижения этой функциональности в C ++ при сохранении всех требуемых свойств: никаких изменений в существующих классах, работа с шаблонами,поддерживает плагины и является кроссплатформенным?
Обновление 1:
Этот проект использует инфраструктуру Qt для многих вещей, включая инфраструктуру плагинов.Qt действительно помогает в кроссплатформенной разработке.Если вам известно конкретное решение проблемы Qt, это также приветствуется.
Обновление 2: комментарий
nm дал мне понять, что я знаю только о проблеме в теории и имеюна самом деле не проверял это.Поэтому я провел некоторое тестирование как в Windows, так и в Linux, используя следующее определение:
template <class T>
class TypeIdTest {
public:
virtual ~TypeIdTest() {};
static int data;
};
template <class T> int TypeIdTest<T>::data;
Этот класс создается в двух разных общих библиотеках / DLL с T = int.Обе библиотеки явно загружаются во время выполнения.Вот что я нашел:
В Linux все просто работает:
- Два экземпляра использовали один и тот же vtable .
- Объект, возвращаемый
typeid
, был по тому же адресу.
- Даже элемент статических данных был таким же.
- Таким образом, тот факт, что шаблон был создан в нескольких динамически загружаемых общих библиотеках, не имеет абсолютно никакого значения. Компоновщик, похоже, просто использует первый загруженный экземпляр и игнорирует остальные.
В Windows эти два экземпляра 'несколько' различны:
-
typeid
для разных экземпляров возвращает type_info
объектов по разным адресам. Эти объекты, однако, равны при тестировании с ==
. Соответствующие хеш-коды также равны. Похоже, что в Windows равенство между типами устанавливается с использованием имени типа, что имеет смысл. Пока все хорошо.
- Однако vtables для двух экземпляров были разными. Я не уверен, насколько это проблема. В моих тестах я мог использовать
dynamic_cast
для понижения экземпляра TypeIdTest
до производного типа через границы разделяемой библиотеки.
- Проблема также в том, что каждый экземпляр использовал свою собственную копию статического поля
data
. Это может вызвать много проблем и в основном запрещает использование статических полей в шаблонных классах.
В целом, похоже, что даже в Windows дела обстоят не так плохо, как я думал, но я все еще неохотно использую этот подход, поскольку в экземплярах шаблонов все еще используются различные vtables и статическое хранилище. Кто-нибудь знает, как избежать этой проблемы? Я не нашел никакого решения.