Множественное наследование интерфейсов в Stack Overflow

У меня есть объектный интерфейс и открытая коллекция интерфейсов, которые может поддерживать производный объект.

// An object
class IObject
{
getAttribute() = 0
}

// A mutable object
class IMutable
{
setAttribute() = 0
}

// A lockable object
class ILockable
{
lock() = 0
}

// A certifiable object
class ICertifiable
{
setCertification() = 0
getCertification() = 0
}

Некоторые производные объекты могут выглядеть так:

class Object1 : public IObject, public IMutable, public ILockable {}
class Object2 : public IObject, public ILockable, public ICertifiable {}
class Object3 : public IObject {}

Вот мой вопрос: есть ли способ написать функции, которые будут принимать только определенные комбинации этих интерфейсов? Например:

void doSomething(magic_interface_combiner<IObject, IMutable, ILockable> object);

doSomething( Object1() )  // OK, all interfaces are available.
doSomething( Object2() )  // Compilation Failure, missing IMutable.
doSomething( Object3() )  // Compilation Failure, missing IMutable and ILockable.

Самая близкая вещь, которую я нашел, это boost :: mpl :: inherit. У меня был некоторый ограниченный успех, но он не делает то, что мне нужно.

Например:

class Object1 : public boost::mpl::inherit<IObject, IMutable, ILockable>::type
class Object2 : public boost::mpl::inherit<IObject, ILockable, ICertifiable>::type
class Object3 : public IObject

void doSomething(boost::mpl::inherit<IObject, ILockable>::type object);

doSomething( Object1() )  // Fails even though Object1 derives from IObject and ILockable.
doSomething( Object2() )  // Fails even though Object2 derives from IObject and ILockable.

Я думаю, что-то похожее на boost :: mpl :: наследовать, но это будет генерировать дерево наследования со всеми возможными перестановками предоставленных типов может работать.

Мне также интересно узнать о других подходах к решению этой проблемы. В идеале это то, что делает проверку времени компиляции, а не время выполнения (т.е. не динамический_каст).

6

Решение

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

template<typename... Interfaces>
struct check_interfaces;
template<>
struct check_interfaces<> {
template<typename T> check_interfaces(T *) {}
};
template<typename Interface, typename... Interfaces>
struct check_interfaces<Interface, Interfaces...>:
public check_interfaces<Interfaces...> {
template<typename T> check_interfaces(T *t):
check_interfaces<Interfaces...>(t), i(t) {}
Interface *i;
operator Interface *() const { return i; }
};

Например:

struct IObject { virtual int getAttribute() = 0; };
struct IMutable { virtual void setAttribute(int) = 0; };
struct ILockable { virtual void lock() = 0; };

void f(check_interfaces<IObject, IMutable> o) {
static_cast<IObject *>(o)->getAttribute();
static_cast<IMutable *>(o)->setAttribute(99);
}

struct MutableObject: IObject, IMutable {
int getAttribute() { return 0; }
void setAttribute(int) {}
};

struct LockableObject: IObject, ILockable {
int getAttribute() { return 0; }
void lock() {}
};

int main() {
f(new MutableObject);
f(new LockableObject);  // fails
}

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

3

Другие решения

Решение с использованием std::enable_if а также std::is_base_of:

#include <type_traits>

// An object
struct IObject
{
virtual void getAttribute() = 0;
};

// A mutable object
struct IMutable
{
virtual void setAttribute() = 0;
};

// A lockable object
struct ILockable
{
virtual void lock() = 0;
};

// A certifiable object
struct ICertifiable
{
virtual void setCertification() = 0;
virtual void getCertification() = 0;
};

struct Object1 : public IObject, public IMutable, public ILockable
{
void getAttribute() {}
void setAttribute() {}
void lock() {}
};

struct Object2 : public IObject, public ILockable, public ICertifiable
{
void getAttribute() {}
void lock() {}
void setCertification() {}
void getCertification() {}
};

struct Object3 : public IObject
{
void getAttribute() {}
};

template<typename T>
void doSomething(
typename std::enable_if<
std::is_base_of<IObject, T>::value &&
std::is_base_of<IMutable, T>::value &&
std::is_base_of<ILockable, T>::value,
T>::type& obj)
{
}

int main()
{
Object1 object1;
Object2 object2;
Object3 object3;

doSomething<Object1>(object1);  // Works
doSomething<Object2>(object2);  // Compilation error
doSomething<Object3>(object3);  // Compilation error
}
2

Вы должны использовать static_assert чтобы проверить типы внутри функции:

#include <type_traits>

template< typename T >
void doSomething( const T& t )
{
static_assert( std::is_base_of<IObject,T>::value, "T does not satisfy IObject" );
static_assert( std::is_base_of<IMutable,T>::value, "T does not satisfy IMutable" );

// ...
}

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

#include <type_traits>

template< typename T, typename... Is >
struct HasInterfaces;

template< typename T >
struct HasInterfaces< T > : std::true_type {};

template< typename T, typename I, typename... Is >
struct HasInterfaces< T, I, Is... >
: std::integral_constant< bool,
std::is_base_of< I, T >::value && HasInterfaces< T, Is... >::value > {};

template< typename T >
typename std::enable_if< HasInterfaces< T, IObject, IMutable >::value >::type
doSomething( const T& t )
{
// ...
}

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

2

Возможно, это не самый элегантный способ, так как он сделан с синтаксисом C ++ 03

template <typename T, typename TInterface>
void interface_checker(T& t)
{
TInterface& tIfClassImplementsInterface = static_cast<TInterface&>(t);
}

Это чтобы дать вам дух уловки.
Теперь в вашем случае:

template <typename T, typename TInterface1, typename TInterface2, typename TInterface3 >
void magic_interface_combiner(T& t)
{
TInterface1& tIfClassImplementsInterface = static_cast<TInterface1&>(t);
TInterface2& tIfClassImplementsInterface = static_cast<TInterface2&>(t);
TInterface3& tIfClassImplementsInterface = static_cast<TInterface3&>(t);
}

Я думаю, это можно сделать намного умнее, используя черты типа C ++ 11.

1

Просто чтобы дать вам небольшой вкус C ++ 11:

Одиночный тип без рекурсии:

template <typename... Ts>
class magic_interface_combiner {
typedef std::tuple<Ts*...> Tpl;
Tpl tpl;

template <typename T, int I>
T *as_(std::false_type)
{
static_assert(I < std::tuple_size<Tpl>::value, "T not found");
return as_<T, I+1>(std::is_same<T, typename std::tuple_element<I+1, Tpl>::type>{});
}
template <typename T, int I>
T *as_(std::true_type) { return std::get<I>(tpl); }

public:
template <typename T>
magic_interface_combiner(T * t) : tpl(static_cast<Ts*>(t)...) {}

template <typename T> T * as() { return as_<T, 0>(std::false_type{}); }
};

// no template
void doSomething(magic_interface_combiner<IObject, IMutable, ILockable> object)
{
}

Два типа, но без рекурсии:

template <typename T>
class single_interface_combiner {
T *p;
public:
single_interface_combiner(T *t) : p(t) {}
operator T* () { return p; }
};

template <typename... Ts>
struct magic_interface_combiner : single_interface_combiner<Ts>... {
template <typename T>
magic_interface_combiner(T* t) : single_interface_combiner<Ts>(t)... {}

template <typename T>
T * as() { return *this; }
};
1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector