Я использую псевдоинтерфейсы в C++, то есть, чистых абстрактных классах. Предположим, что у меня есть три интерфейса, IFoo, IBar и IQuux. У меня также есть класс Fred, который реализует все три из них:
interface IFoo
{
void foo (void);
}
interface IBar
{
void bar (void);
}
interface IQuux
{
void quux (void);
}
class Fred : implements IFoo, IBar, IQuux
{
}
Я хочу объявить метод, который принимает любой объект, который реализует IFoo и IBar - Fred работал бы, например. Единственное время компиляции способ сделать это, которое я могу вообразить, должно определить третий интерфейс IFooAndBar, который реализует обоих, и повторно объявите Fred:
interface IFooAndBar : extends IFoo, IBar
{
}
class Fred : implements IFooAndBar, IQuux
{
}
Теперь я могу объявить свой метод как получение IFooAndBar*.Пока все хорошо.
Однако, что происходит, если я также хочу другой метод, который принимает IBar и IQuux? Я пытался объявить новый интерфейс IBarAndQuux и объявить Fred как наследовавший обоих:
class IFooAndBar : IFoo, IBar
{
};
class IBarAndQuux : IBar, IQuux
{
};
class Fred : IFooAndBar, IBarAndQuux
{
};
Это работает, когда я передаю Fred как IFooAndBar к методу; однако, когда я пытаюсь позвонить Fred:: панель () непосредственно, gcc жалуется:
error: request for member ‘bar’ is ambiguous
error: candidates are: void IBar::bar()
error: void IBar::bar()
который делает это решение более или менее бесполезным.
Моя следующая попытка состояла в том, чтобы объявить Fred как наследовавшийся трем отдельным интерфейсам и заставляющий метод принять один из гибридных интерфейсов в качестве параметра:
class Fred : public IFoo, public IBar, public IBaz
{
};
void doTest (IBarAndBaz* pObj)
{
pObj->bar();
pObj->baz();
}
Когда я пытаюсь передать Fred как IBarAndBaz* параметр, я получаю ошибку, как ожидалось:
error: cannot convert ‘Fred*’ to ‘IBarAndBaz*’ for argument ‘1’ to ‘void doTest(IBarAndBaz*)’
dynamic_cast <> также производит ошибку (который я не понимаю),
error: cannot dynamic_cast ‘pFred’ (of type ‘class Fred*’) to type ‘class IBarAndBaz*’ (source type is not polymorphic)
Принуждение броска действительно работает, однако:
doTest((IBarAndBaz*)pFred);
но интересно, насколько безопасный и портативный это (я разрабатываю для Linux, Mac и Windows), и работает ли он в реальной ситуации.
Наконец, я понимаю, что мой метод может принять, что указатель на один из интерфейсов и dynamic_cast к другому (s) осуществляет корректный тип параметра во времени выполнения, но я предпочитаю решение времени компиляции.
Подумайте об использовании проверенных решений - Boost.typeepuit к спасению:
template<class T>
void takeFooAndBar(const T& t) {
BOOST_STATIC_ASSERT(
boost::is_base_of<IFoo, T>::value
&& boost::is_base_of<IBar, T>::value);
/* ... */
}
Вы можете добиться эффекта с использованием шаблона MetaProgramming:
tempate<class C>
void doTest( C* pObj )
{
pObj->bar();
pObj->baz();
}
будет вести себя правильно для классов, которые питаются стержня () и BAZ () и не скомпилируются для любых других классов.
Что вы можете сделать, это создать класс с помощью шаблонного конструктора, который принимает произвольный указатель, использует неявное нагрузку, чтобы получить два интерфейса, которые вы хотите, а затем реализуют комбинированный интерфейс.
struct IFoo
{
virtual void foo() = 0;
};
struct IBar
{
virtual void bar() = 0;
};
struct IFooAndBar : public IFoo, public IBar {};
class FooAndBarCompositor : public IFooAndBar
{
public:
template <class T>
FooAndBarCompositor(T* pImpl) : m_pFoo(pImpl), m_pBar(pImpl) {}
void foo() {m_pFoo->foo();}
void bar() {m_pBar->bar();}
private:
IFoo* m_pFoo;
IBar* m_pBar;
};
Затем вы пишете функцию, которая принимает IFOONANDBAR *, если требуются оба интерфейса, и вызывающий абонент может построить FOOANDBARCOMPOSITOR на стеке, который отправляет к объекту их выбора. Похоже:
void testFooAndBar(IFooAndBar* pI) {}
void baz(Fred* pFred)
{
FooAndBarCompositor fb(pFred);
testFooAndBar(&fb);
}
Это не очень общее, и заставляет вас писать диспетчерские функции в композиторе. Другой подход состоит состоит в том, чтобы иметь общий шаблон композиторов интерфейса:
template <class IA, class IB>
class InterfaceCompositor
{
public:
template <class T>
InterfaceCompositor(T* pObj) : m_pIA(pObj), m_pIB(pObj) {}
IA* AsA() const {return m_pIA;}
operator IA* () const {return AsA();}
IB* AsB() cosnt {return m_pIB;}
operator IB* () const {return AsB();}
private:
IA* m_pIA;
IB* m_pIB;
};
, то функция выглядит как:
void testFooAndBar(InterfaceCompositor<IFoo, IBar> pI)
{
IFoo* pFoo = pI; // Or pI.AsA();
IBar* pBar = pI; // Of pI.AsB();
}
Это требует функции, которая хочет обеспечить установление нескольких интерфейсов для использования композитора, где ожидается A * или B * (например, Назначение или параметр функции) или явно вызовите соответствующий метод ASX (). В частности, интерфейс для использования не может быть выведен с использованием оператора «>», а оператор * не имеет смысла на композите.
Если вы переходите с универсальным кодом, вы можете использовать тот же шаблон для обеспечения применения того, что объект поддерживает как IBAR, так и IBAZ.
C ++ 0x введет вариационные шаблоны, которые позволят эту концепцию продлевать до произвольного количества интерфейсных классов.
Для этого в стиле OO нужна виртуальная наследование, чтобы убедиться, что fred
заканчивается только одной копией IBAR
:
class IFooAndBar : public IFoo, public virtual IBar {};
class IBarAndQuux : public virtual IBar, public IQuux {};
class Fred : public IFooAndBar, public IBarAndQuux {};
Fred fred;
fred.bar(); // unambiguous due to virtual inheritence
Другие сказали, вы можете сделать что-то похожее на вашу вторую попытку, используя шаблоны для получения статического полиморфизма.
Литые, которые вы пытались, не возможно, в качестве экземпляра Фред
не является экземпляром IBARANDBAZ
. Принудительные литые компилирования, потому что большинство принудительных отливок будут скомпилировать, независимо от того, безопасно ли преобразование, но в этом случае он даст неопределенное поведение.
Редактировать : В качестве альтернативы, если вы не хотите использовать шаблоны и не любите комбинаторное взрыв определения всех возможных групп интерфейсов, вы можете определить функции для получения каждого интерфейса в качестве отдельного параметра:
void doTest(IBar *bar, IBaz *baz)
{
bar->bar();
baz->baz();
}
class Fred : public IBar, public IBaz {};
Fred fred;
doTest(&fred,&fred);