Динамическое приведение с интерфейсами

У меня есть класс с двумя интерфейсами и наследует 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() в одном из моих интерфейсов.

Какие ошибки возникают при использовании dynamic_cast? Я ожидал, что это сработает.

e.James 14.01.2009 08:40

В результате приведения я получаю нулевой указатель.

Evan Teran 14.01.2009 08:48
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
2
10 572
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Сначала приведите к 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, который недоступен там, где это необходимо.

Evan Teran 14.01.2009 08:18

Итак, вы говорите, что у вас есть тип T, который, как вы знаете, наследуется от A и IB, но не может получить доступ к T?

Eclipse 14.01.2009 08:30

да, моя архитектура плагинов (на основе QT) такова, что плагин наследуется от A (QObject) и реализует мой интерфейс. Позже система QT предоставляет мне экземпляр (первоначально QObject *, который я динамически преобразовал в тип интерфейса моего плагина.

Evan Teran 14.01.2009 08:32

К сожалению, у меня есть плагин, который реализует 2 интерфейса, а второй не имеет реального отношения к основному интерфейсу плагина.

Evan Teran 14.01.2009 08:33

У каждого класса есть хотя бы один виртуальный метод? Если нет, то в этом твоя проблема. Добавление виртуального деструктора к каждому классу должно решить эту проблему.

У меня сработало следующее:

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;
}

Я не утверждаю, что это правильное решение, но оно может дать некоторую информацию о том, что происходит ...

Да, у них есть виртуальные методы. Оба интерфейса полностью состоят из чистых виртуальных функций (и виртуального деструктора, который ничего не делает).

Evan Teran 14.01.2009 08:39

Интересно, что ваш пример работает ... Тем не менее, мне нужно подробнее изучить свой код, поскольку я считаю, что он соответствует моему сценарию.

Evan Teran 14.01.2009 08:41

А класс А? Если все классы, включая последний класс, имеют виртуальные методы, то вы меня победили. Это должно просто работать. Что-то компилировалось без RTTI?

Daniel Paull 14.01.2009 08:41

да, я даже добавил метод к A и вызвал его, чтобы убедиться, что приведение прошло успешно. Возможно, напишите небольшое приложение для командной строки и убедитесь, что приведенный выше пример работает с вашей средой.

Daniel Paull 14.01.2009 08:42

Я считаю, что rtti включен (иначе я бы получил ошибку на dynamic_cast, верно?)

Evan Teran 14.01.2009 08:43

Что за ошибка? Нулевой указатель или исключение?

Eclipse 14.01.2009 08:44

@ Дэниел Пол, откуда ты знаешь, что это работает? Вы никогда не тестируете a_ptr. dynamic_cast вернет null в случае ошибки.

zdan 14.01.2009 08:44

Вызов dyamic_cast <> должен быть предупреждением компилятора, если RTTI выключен. Однако я не уверен, что произошло бы, если бы фреймворк был скомпилирован с RTTI, а плагин - без него. Вы получили исключение? Или нулевой возврат? Или авария? Я не знаю, каково ожидаемое поведение!

Daniel Paull 14.01.2009 08:45

@zdan: отладчик показал, что вернул ненулевое значение, и, как отмечалось выше, я даже добавил метод к классу A и вызвал его, чтобы убедиться, что указатель работает. Я изменю код в сообщении, чтобы отразить это.

Daniel Paull 14.01.2009 08:47

@Evan: Я думаю, вам следует вернуться к основам и попробовать скомпилировать приведенный выше код и убедиться, что он работает для вас - если это так, ищите проблему в своем приложении. Если это не удается, ищите проблему в своих инструментах!

Daniel Paull 14.01.2009 08:51

Я использую g ++ (4.3.2), и приведенные вами примеры работают нормально. Я предполагаю, что проблема с объектом из разных разделяемых библиотек.

Evan Teran 14.01.2009 08:57

Он должен нормально работать через границы DLL, если обе DLL содержат информацию RTTI. Чувак, вы находитесь в сложной ситуации, потому что ваше динамическое приведение должно работать нормально ... Я не уверен, что вы должны делать, кроме как начать читать RTTI для вашего компилятора.

Daniel Paull 14.01.2009 09:01

Да, я знаю, что QT позволяет отключать RTTI, но я думаю, что он включен по умолчанию. Придется все это перепроверить. (Я действительно вижу записи «typeinfo для ________» в каждом выводе nm плагинов). Мне просто придется продолжать это делать.

Evan Teran 14.01.2009 09:06

Удачи в этом деле - когда вы его взломаете, опубликуйте свои выводы, так как мне не терпится узнать, что вызвало такое поведение.

Daniel Paull 14.01.2009 09:15

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 не может работать.

Evan Teran 14.01.2009 08:45
Ответ принят как подходящий

Я наконец понял это, Дэниел Полл был правильным в том, что "боковой dybnamic_cast" должен быть разрешен. Моя проблема заключалась в том, что мой код использует разделяемые библиотеки. Информация о типе из PluginA не была доступна в PluginB. Мое решение заключалось в том, чтобы эффективно добавить RTLD_NOW и RTLD_GLOBAL в мой процесс загрузки.

технически это было

loader.setLoadHints(QLibrary::ResolveAllSymbolsHint | QLibrary::ExportExternalSymbolsHint);

потому что я использую систему плагинов Qt, но с такой же разницей. Эти флаги заставляют все символы из загруженных библиотек немедленно разрешаться и быть видимыми для других библиотек. Таким образом, мы делаем typeinfo доступным для всех, кто в этом нуждается. dynamic_cast работал, как ожидалось, после установки этих флагов.

Рад слышать, что вы все поняли!

Eclipse 26.01.2009 07:55

Меня также недавно беспокоила такая же проблема. Дополнительные сведения см. В разделе часто задаваемых вопросов GCC:

http://gcc.gnu.org/faq.html#dso

Помимо указания dlopen с помощью флагов RTLD_ *, некоторые варианты этой проблемы также могут быть решены компоновщиком, см. Его параметры -E и -Bsymbolic.

Другие вопросы по теме