Проверка реализации интерфейса во время компиляции в C ++ - PullRequest
5 голосов
/ 18 января 2010

Я использую псевдоинтерфейсы в 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 и объявить Фреда как унаследованного:

class IFooAndBar : IFoo, IBar
{
};


class IBarAndQuux : IBar, IQuux
{
};


class Fred : IFooAndBar, IBarAndQuux
{
};

Это работает, когда я передаю Фреду метод IFooAndBar методу;однако, когда я пытаюсь вызвать Fred :: bar () напрямую, gcc жалуется:

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 <> также выдает ошибку (которую я не понимаю)

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

Ответы [ 4 ]

7 голосов
/ 18 января 2010

Сначала подумайте об использовании проверенных решений - 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);
    /* ... */
}
3 голосов
/ 18 января 2010

Чтобы сделать это в стиле 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

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

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

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

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

class Fred : public IBar, public IBaz {};

Fred fred;
doTest(&fred,&fred);
1 голос
/ 18 января 2010

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

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

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

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

1 голос
/ 18 января 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...