динамическое приведение с интерфейсами - PullRequest
7 голосов
/ 14 января 2009

У меня есть класс с реализует 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 *. Мой пример имеет рядом друг с другом, но, как уже упоминалось, это сложнее В основном у меня есть 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.
};

EDIT : Я предполагаю, что проблема в том, что pluginA и pluginB находятся в разных общих библиотеках. Возможно, RTI не пересекает границы модуля. Я думаю, что это может иметь место, потому что примеры людей, кажется, работают хорошо в моих тестах. В частности, у плагина B нет «typeinfo для PluginA», если я использую «nm» на нем. Это может быть ядром проблемы. Если это так, мне просто придется обойти это либо виртуальным наследованием, либо виртуальной функцией cast_to_qobject() в одном из моих интерфейсов.

Ответы [ 5 ]

7 голосов
/ 14 января 2009

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

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

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

EDIT:

После всей новой информации и необычного поведения (ваш код должен просто работать!), Поможет ли следующее? Я ввел интерфейс под названием 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;
}

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

5 голосов
/ 14 января 2009

Есть ли правильный способ сделать это? Или я должен осуществить обход? Я думал о том, что IB и IC наследуются практически от А, но в прошлый раз 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; }
};
3 голосов
/ 24 января 2009

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

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

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

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

3 голосов
/ 14 января 2009

Сначала приведение к 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.

Edit: Я только что попробовал это, и это работает - обратите внимание, что 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(); }
1 голос
/ 20 июля 2009

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

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

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

...