У меня есть класс с двумя интерфейсами и наследует 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 *. В моем примере они расположены рядом друг с другом, но, как уже упоминалось, все сложнее. В основном у меня есть 2 общие библиотеки. T и T1 (где у меня есть IB *) - это классы, которые реализуют API плагина и являются внутренними по отношению к разделяемым библиотекам.
Чтобы уточнить: вот более конкретный пример моих типичных плагинов (они находятся в отдельных библиотеках):
плагин A:
class PluginA : public QObject, public PluginInterface, public OtherInterface {
};
плагин B:
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.
};
РЕДАКТИРОВАТЬ: Я предполагаю, что проблема в том, что pluginA и pluginB находятся в разных разделяемых библиотеках. Возможно, rtti не пересекает границы модуля. Я думаю, что это может быть так, потому что примеры людей, кажется, отлично работают в моих тестах. В частности, pluginB не имеет «typeinfo для PluginA», если я использую для него «nm». Это может быть сутью проблемы. Если это так, мне просто придется обойти это либо виртуальным наследованием, либо виртуальной функцией cast_to_qobject() в одном из моих интерфейсов.
В результате приведения я получаю нулевой указатель.





Сначала приведите к 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(); }
Хороший ответ, но, к сожалению, моя арка сложнее примера. T - это реализация плагина API, который недоступен там, где это необходимо.
Итак, вы говорите, что у вас есть тип T, который, как вы знаете, наследуется от A и IB, но не может получить доступ к T?
да, моя архитектура плагинов (на основе QT) такова, что плагин наследуется от A (QObject) и реализует мой интерфейс. Позже система QT предоставляет мне экземпляр (первоначально QObject *, который я динамически преобразовал в тип интерфейса моего плагина.
К сожалению, у меня есть плагин, который реализует 2 интерфейса, а второй не имеет реального отношения к основному интерфейсу плагина.
У каждого класса есть хотя бы один виртуальный метод? Если нет, то в этом твоя проблема. Добавление виртуального деструктора к каждому классу должно решить эту проблему.
У меня сработало следующее:
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;
}
Я не утверждаю, что это правильное решение, но оно может дать некоторую информацию о том, что происходит ...
Да, у них есть виртуальные методы. Оба интерфейса полностью состоят из чистых виртуальных функций (и виртуального деструктора, который ничего не делает).
Интересно, что ваш пример работает ... Тем не менее, мне нужно подробнее изучить свой код, поскольку я считаю, что он соответствует моему сценарию.
А класс А? Если все классы, включая последний класс, имеют виртуальные методы, то вы меня победили. Это должно просто работать. Что-то компилировалось без RTTI?
да, я даже добавил метод к A и вызвал его, чтобы убедиться, что приведение прошло успешно. Возможно, напишите небольшое приложение для командной строки и убедитесь, что приведенный выше пример работает с вашей средой.
Я считаю, что rtti включен (иначе я бы получил ошибку на dynamic_cast, верно?)
Что за ошибка? Нулевой указатель или исключение?
@ Дэниел Пол, откуда ты знаешь, что это работает? Вы никогда не тестируете a_ptr. dynamic_cast вернет null в случае ошибки.
Вызов dyamic_cast <> должен быть предупреждением компилятора, если RTTI выключен. Однако я не уверен, что произошло бы, если бы фреймворк был скомпилирован с RTTI, а плагин - без него. Вы получили исключение? Или нулевой возврат? Или авария? Я не знаю, каково ожидаемое поведение!
@zdan: отладчик показал, что вернул ненулевое значение, и, как отмечалось выше, я даже добавил метод к классу A и вызвал его, чтобы убедиться, что указатель работает. Я изменю код в сообщении, чтобы отразить это.
@Evan: Я думаю, вам следует вернуться к основам и попробовать скомпилировать приведенный выше код и убедиться, что он работает для вас - если это так, ищите проблему в своем приложении. Если это не удается, ищите проблему в своих инструментах!
Я использую g ++ (4.3.2), и приведенные вами примеры работают нормально. Я предполагаю, что проблема с объектом из разных разделяемых библиотек.
Он должен нормально работать через границы DLL, если обе DLL содержат информацию RTTI. Чувак, вы находитесь в сложной ситуации, потому что ваше динамическое приведение должно работать нормально ... Я не уверен, что вы должны делать, кроме как начать читать RTTI для вашего компилятора.
Да, я знаю, что QT позволяет отключать RTTI, но я думаю, что он включен по умолчанию. Придется все это перепроверить. (Я действительно вижу записи «typeinfo для ________» в каждом выводе nm плагинов). Мне просто придется продолжать это делать.
Удачи в этом деле - когда вы его взломаете, опубликуйте свои выводы, так как мне не терпится узнать, что вызвало такое поведение.
Is there a proper way to do this? Or should I implement a work around? I've thought about having both IB and IC inherit virtually from A, but IIRC last time I tried that there were some complications that made it undesirable.
Я так понимаю, что определения 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; }
};
да, метод castToA выглядит жизнеспособным вариантом, если dynamic_cast не может работать.
Я наконец понял это, Дэниел Полл был правильным в том, что "боковой dybnamic_cast" должен быть разрешен. Моя проблема заключалась в том, что мой код использует разделяемые библиотеки. Информация о типе из PluginA не была доступна в PluginB. Мое решение заключалось в том, чтобы эффективно добавить RTLD_NOW и RTLD_GLOBAL в мой процесс загрузки.
технически это было
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint | QLibrary::ExportExternalSymbolsHint);
потому что я использую систему плагинов Qt, но с такой же разницей. Эти флаги заставляют все символы из загруженных библиотек немедленно разрешаться и быть видимыми для других библиотек. Таким образом, мы делаем typeinfo доступным для всех, кто в этом нуждается. dynamic_cast работал, как ожидалось, после установки этих флагов.
Рад слышать, что вы все поняли!
Меня также недавно беспокоила такая же проблема. Дополнительные сведения см. В разделе часто задаваемых вопросов GCC:
http://gcc.gnu.org/faq.html#dso
Помимо указания dlopen с помощью флагов RTLD_ *, некоторые варианты этой проблемы также могут быть решены компоновщиком, см. Его параметры -E и -Bsymbolic.
Какие ошибки возникают при использовании dynamic_cast? Я ожидал, что это сработает.