динамическое приведение с интерфейсами
-
22-07-2019 - |
Вопрос
У меня есть класс, реализующий 2 интерфейса и наследующий 1 класс.Итак, в целом это выглядит так:
class T : public A, public IB, public IC {
};
В коде есть один момент, где у меня есть IB *
, но действительно мог бы использовать A *
.Я надеялся, что динамичному актерскому составу понравится вот это:
IB *b_ptr = new T; // it's really more complicated, but serves the example
A *a_ptr = dynamic_cast<A *>(b_ptr);
к сожалению, это не работает.Есть ли правильный способ сделать это?Или мне следует реализовать обходной путь?Я думал о том, чтобы иметь оба IB
и IC
наследовать практически от A
, но в прошлый раз, когда я пытался IIRC, возникли некоторые осложнения, которые сделали это нежелательным.
Есть предположения?
РЕДАКТИРОВАТЬ:о да, это часть API плагина, поэтому, к сожалению, у меня нет прямого доступа к T
напишите, где мне нужно A *
.В моем примере они расположены рядом друг с другом, но, как уже упоминалось, это сложнее.По сути, у меня есть две общие библиотеки. T
и T1
(где у меня есть IB *
) являются классами, которые реализуют API плагинов и являются внутренними для общих библиотек.
Чтобы уточнить:Вот более конкретный пример моих типичных плагинов (они находятся в отдельных библиотеках):
плагин А:
class PluginA : public QObject, public PluginInterface, public OtherInterface {
};
плагин Б:
class PluginB : public QObject, public PluginInterface {
// in here, I have a PluginInterface *, but really could use a QObject *
// unfortunately, PluginB has absolutely no knowledge of the "PluginA" type
// it just so happens that my PluginInterface * pointer points to an object of type
// PluginA.
};
РЕДАКТИРОВАТЬ:Я предполагаю, что проблема в том, что плагин A и плагин B находятся в разных общих библиотеках.Возможно, rtti не выходит за границы модулей.Я думаю, что это может быть так, потому что примеры людей, похоже, отлично работают в моих тестах.В частности, у плагина B нет «typeinfo для плагина A», если я добавляю к нему «nm».Возможно, в этом суть проблемы.Если это так, мне просто придется обойти это с помощью виртуального наследования или виртуального наследования. cast_to_qobject()
функция в одном из моих интерфейсов.
Решение 4
Я наконец-то понял это, Даниэль Полл был прав в этом " в сторону dybnamic_cast
" должно быть разрешено. Моя проблема была в том, что в моем коде используются общие библиотеки. Информация о типе из PluginA не была доступна в PluginB. Мое решение состояло в том, чтобы эффективно добавить RTLD_NOW
и RTLD_GLOBAL
в процесс загрузки
технически это было
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint | QLibrary::ExportExternalSymbolsHint);
потому что я использую систему плагинов Qt, но та же разница. Эти флаги заставляют все символы из загруженных библиотек быть немедленно разрешенными и видимыми для других библиотек. Поэтому сделав typeinfo доступным для всех, кому это нужно. dynamic_cast
работал должным образом, как только эти флаги были установлены.
Другие советы
Есть ли у каждого класса хотя бы один виртуальный метод?Если нет, то это ваша проблема.Добавление виртуального деструктора в каждый класс должно решить проблему.
У меня с радостью сработало следующее:
class IC
{
public:
virtual ~IC() {}
};
class IB
{
public:
virtual ~IB() {}
};
class A
{
public:
virtual ~A() {}
void foo() { /* stick a breakpoint here to confirm that this is called */ }
};
class T : public A, public IB, public IC
{
public:
virtual ~T() {}
};
int main(void)
{
IB *b_ptr = new T;
A *a_ptr = dynamic_cast<A *>(b_ptr);
a_ptr->foo();
return 0;
}
РЕДАКТИРОВАТЬ:
После всей новой информации и необычного поведения (ваш код должен работать!), поможет ли следующее?Я представил интерфейс под названием IObject и использую виртуальное наследование, чтобы гарантировать наличие только одной копии этого базового класса.Можете ли вы теперь выполнить приведение к IObject, а затем к A?
class IObject
{
public:
virtual ~IObject() {}
};
class IC : virtual public IObject
{
public:
virtual ~IC() {}
};
class IB : virtual public IObject
{
public:
virtual ~IB() {}
};
class A : virtual public IObject
{
public:
virtual ~A() {}
void foo() { /* stick a breakpoint here to confirm that this is called */ }
};
class T : virtual public A, virtual public IB, virtual public IC
{
public:
virtual ~T() {}
};
int main()
{
IB *b_ptr = new T;
A *a_ptr = dynamic_cast<A *>( dynamic_cast<IObject *>(b_ptr) );
a_ptr->foo();
return 0;
}
Я не утверждаю, что это правильное решение, но оно может дать некоторую информацию о том, что происходит...
Есть ли правильный способ сделать это?Или мне следует реализовать обходной путь?Я думал о том, чтобы IB и IC фактически наследовались от A, но в прошлый раз, когда я пытался IIRC, возникли некоторые сложности, которые сделали это нежелательным.
Я так понимаю, определения IB и IC находятся под вашим контролем.
Вот как COM-интерфейсы работают в Windows;они делают то, что вы хотите, то есть:
- Трансляция с одного интерфейса на другой
- Реализация непрозрачна для вызывающей стороны
- Только реализация знает, какие интерфейсы она реализует.
Сделайте это, вы можете сделать что-то вроде (впереди непроверенный код)...
interface IQueryInterface
{
IQueryInterface* queryInterface(const Guid* interfaceId);
};
interface IB : public abstract IQueryInterface
{
...
};
interface IC : public abstract IQueryInterface
{
...
};
//within your implementation class
IQueryInterface* T::queryInterface(const Guid* interfaceId)
{
if (matches(interfaceId,GUID_IB))
return (IB*)this;
if (matches(interfaceId,GUID_IC))
return (IC*)this;
if (matches(interfaceId,GUID_A))
return (A*)this;
return 0;
}
Гораздо более простая и жестко запрограммированная версия этого будет:
class A; //forward reference
interface IB
{
virtual A* castToA() { return 0; }
};
class T : public A, IB, IC
{
virtual A* castToA() { return this; }
};
Сначала приведите к T*, затем к A:
IB *b_ptr = new T; // it's really more complicated, but serves the example
A *a_ptr = dynamic_cast<T *>(b_ptr);
Если IB вообще должен быть приведён к A, то, возможно, IB должен наследовать от A.
Редактировать:Я только что попробовал это, и это работает — обратите внимание, что E неизвестен на момент компиляции основного метода.
struct A
{
virtual ~A() {}
};
struct C
{
virtual ~C() {}
};
A* GetA();
int main()
{
C *y = dynamic_cast<C *>(GetA());
if (y == NULL)
cout << "Fail!";
else
cout << "Ok!";
}
struct E : public A, public C
{
};
A* GetA() { return new E(); }
Недавно я столкнулся с такой же проблемой. Для получения дополнительной информации см. Раздел часто задаваемых вопросов GCC:
http://gcc.gnu.org/faq.html#dso р>
Помимо указания dlopen с флагами RTLD_ *, некоторые воплощения этой проблемы также могут быть решены с помощью компоновщика, см. его параметры -E и -Bsymbolic.