2010-01-18 1 views
5

Я использую псевдоинтерфейсы в C++, то есть чистые абстрактные классы. Предположим, у меня есть три интерфейса: IFoo, IBar и IQuux. У меня также есть класс Фред, который реализует все три из них:Проверка реализации интерфейса времени компиляции в C++

interface IFoo 
{ 
    void foo (void); 
} 

interface IBar 
{ 
    void bar (void); 
} 

interface IQuux 
{ 
    void quux (void); 
} 

class Fred : implements IFoo, IBar, IQuux 
{ 
} 

Я хочу объявить метод, который принимает любой объект, который реализует IFoo и IBar - это Фред будет работать, например. Только во время компиляции, способ сделать это, я могу себе представить, чтобы определить третий интерфейс IFooAndBar, который реализует как и переобъявить Фред:

interface IFooAndBar : extends IFoo, IBar 
{ 
} 

class Fred : implements IFooAndBar, IQuux 
{ 
} 

Теперь я могу объявить свой метод как получение IFooAndBar *. Все идет нормально.


Однако, что произойдет, если я хочу другой метод, который принимает IBar и IQuux? Я попытался объявить новый интерфейс IBarAndQuux и объявить Фред, как наследование как:

class IFooAndBar : IFoo, IBar 
{ 
}; 


class IBarAndQuux : IBar, IQuux 
{ 
}; 


class Fred : IFooAndBar, IBarAndQuux 
{ 
}; 

Это работает, когда я прохожу Фреда как IFooAndBar к способу; Однако, когда я пытаюсь вызвать Фреда :: бар() непосредственно, НКУ жалуется:

error: request for member ‘bar’ is ambiguous 
error: candidates are: void IBar::bar() 
error:     void IBar::bar() 

, что делает это решение более или менее бесполезно.


Моя следующая попытка была объявить Фред, как унаследовав от трех отдельных интерфейсов и делает метод принимает один из гибридных интерфейсов в качестве параметра:

class Fred : public IFoo, public IBar, public IBaz 
{ 

}; 

void doTest (IBarAndBaz* pObj) 
{ 
    pObj->bar(); 
    pObj->baz(); 
} 

Когда я пытаюсь передать Фред, как IBarAndBaz * параметр, я получаю сообщение об ошибке, как и ожидалось:

error: cannot convert ‘Fred*’ to ‘IBarAndBaz*’ for argument ‘1’ to ‘void doTest(IBarAndBaz*)’ 

< dynamic_cast> также р roduces ошибки (которую я не понимаю)

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 к другим (ы) для обеспечения правильного типа параметров во время выполнения, но я предпочитаю время компиляции решения.

+2

Это небезопасно и портативно. Фактически, он не определен в C++. – 2010-01-18 17:09:46

ответ

6

Рассмотрим с использованием проверенных решений первого - Boost.TypeTraits на помощь:

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); 
    /* ... */ 
} 
+1

Есть мир за пределами boost :-) – mmmmmmmm

+0

Конечно, bcp помогает с этим;) Но если серьезно, зачем дублировать такую ​​всеобъемлющую коллекцию обходных путей компилятора, особенно для черт типа и тому подобного? –

+1

Я не думаю, что это хорошая идея, чтобы выйти из большого пистолета, если нормальный C++ (виртуальное наследование) может решить проблему. – mmmmmmmm

1

Вы можете добиться эффекта с помощью шаблона метапрограммирование:

tempate<class C> 
void doTest(C* pObj) 
{ 
    pObj->bar(); 
    pObj->baz(); 
} 

будет вести себя правильно для классов, которые поставляют бар() и БАЗ() и не могут компилироваться для любых других классов.

+0

Это очень интересно. На самом деле мой реальный случай сложнее, у меня есть класс, который получает объект, который должен реализовывать оба интерфейса, сохраняет указатель на него и затем вызывает его методы. Мне не удобно реализовать этот большой класс в качестве шаблона :( – ggambett

+0

Можно запрограммировать утверждение времени компиляции, что тип предоставляет определенную функцию-член без вызова этой функции. Вы можете сделать это в шаблонной оболочке, которая затем вызывает реальная функция, шаблонный код объявляется с помощью встроенного атрибута, поэтому он компилируется. Тем не менее, реализация является волосатой - проще использовать одну из boost (см. ответ gf), чем рулон. – moonshadow

3

Чтобы сделать это в стиле О.О., вам нужно виртуальное наследование, чтобы гарантировать, что 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 

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

Листинг, который вы пытались, невозможен, поскольку экземпляр Fred не является экземпляром IBarAndBaz. Принудительное компиляция компилируется, потому что большинство принудительных бросков будет скомпилировано, независимо от того, безопасно ли преобразование или нет, но в этом случае оно будет давать неопределенное поведение.

Редактировать: В противном случае, если вы не хотите использовать шаблоны и не любите комбинаторный взрыв, определяющий все возможные группы интерфейсов, вы можете определить функции для каждого интерфейса в качестве отдельного параметра:

void doTest(IBar *bar, IBaz *baz) 
{ 
    bar->bar(); 
    baz->baz(); 
} 

class Fred : public IBar, public IBaz {}; 

Fred fred; 
doTest(&fred,&fred); 
+0

Это может сработать. что я должен был бы объявить все возможные комбинации интерфейсов (по крайней мере, те, которые я использую) в определении Фреда ... было бы намного лучше, если бы он мог быть разрешен в местоположении пользовательского метода, а не в месте расположения параметра, например основанные на шаблонах решения. – ggambett

1

что вы можете сделать, это создать класс с шаблонным конструктором, который принимает произвольный указатель, использует неявное понижающее приведение, чтобы получить два интерфейс, которые вы хотите, а затем реализует комбинированный интерфейс.

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

Тогда вы написать функцию, которая принимает IFooAndBar * если требуется оба интерфейс, и вызывающий абонент может построить FooAndBarCompositor на стеке, который рассылает объект по своему выбору. Он выглядит так:

void testFooAndBar(IFooAndBar* pI) {} 

void baz(Fred* pFred) 
{ 
    FooAndBarCompositor fb(pFred); 
    testFooAndBar(&fb); 
} 

Это не очень общее и заставляет писать функции отправки в компоновщике. Другой подход заключается в общий шаблон интерфейса Compositor:

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

Это требует функцию, которая хочет, чтобы обеспечить соблюдение нескольких интерфейсов либо использовать наборщика, где Л * или B * (например, назначение или параметр функции) или явно вызвать соответствующий метод AsX(). В частности, интерфейс для использования не может быть выведен из использования оператора ->, а оператор * не имеет никакого значения для композита.

Если вы используете общий код, вы можете использовать тот же шаблон для обеспечения того, чтобы объект поддерживал как IBar, так и IBaz.

C++ 0x представит вариативные шаблоны, которые позволят расширить эту концепцию до произвольного числа классов интерфейсов.