Учитывая некоторые базовые интерфейсы A, B, C …. N
И некоторые гипотетические классы, которые реализуют их набор:
class MyClass1: public A, public B, public N;
class MyClass2: public A, public B, public D;
Я хотел бы создать функцию, которая принимает в качестве параметра объект (или указатель на объект) для класса, который включает, скажем, A и B.
void myFunc( A&B obj );
С точки зрения программиста на C ++ это кажется абсурдным. С точки зрения разработчика программного обеспечения, это кажется базовым блоком, который должен быть у всех языков.
Есть ли хитрость (кроме шаблона) или решение для этого?
Примечание: большинство интерфейсов не от меня, поэтому (в принципе) невозможно вносить изменения в интерфейсы.
Редакция:
Вот несколько примеров:
class A{
public:
virtual void draw()=0;
};
class B{
public:
virtual void edit()=0;
};class AandB: public A, public B
{
virtual void draw()override{};
virtual void edit()override{};
};
//This function know about A, B, but not AandB
void some_function((A+B)& a_and_b) { // That do not work
a_and_b.draw();
a_and_b.edit();
}
int main()
{
AandB c;
some_function(c); // that is not so easy
}
То, что вы действительно просите, это компилятор для создания промежуточного типа (A
& B
) чей интерфейс содержит оба интерфейс A
а также интерфейс B
, А затем разрешить любой тип, который реализует и то и другое эти интерфейсы для привязки к ссылке этого комбинированного типа.
Я даже не уверен, как вы можете ссылаться на такой гештальт-тип в синтаксисе:
void func((A+B)& var); // ???
Ну, вы можете сделать то же самое с текущим синтаксисом без компилятора, создающего леса за кулисами, просто принимая параметр дважды как это:
struct A { void stuff(){ std::cout << "A\n"; } };
struct B { void stuff(){ std::cout << "B\n"; } };
struct C { void stuff(){ std::cout << "C\n"; } };
struct MyType: A, B, C
{
void stuff() { std::cout << "MyType\n"; }
};
void func(A& a, B& b) // accept both interfaces in func
{
a.stuff(); // use interface A
b.stuff(); // use interface B
}
int main()
{
MyType m;
func(m, m); // pass both interfaces to func()
}
#include <type_traits>
using namespace std;
class A{};
class B{};
template<class T, enable_if_t<is_same<A, decay_t<T>>::value || is_same<B, decay_t<T>>::value, int> = 0>
void some_function(T&& a_or_b) {}
жить: https://godbolt.org/g/Z1MV8w
Этот пример требует c ++ 14, но вы можете использовать enable_if
вместо этого, если вам нужно 11 совместимости. Это просто не так читабельно.
Если вы хотите взять что-либо, что наследуется от A или B, используйте is_base_of
вместо is_same
Я голосовал за ответ Xaxxon,
но если вы хотите сделать это «виртуальным» способом,
Вы можете сделать класс, который расширяет A
а также B
,
Будет выглядеть так:
class A;
class B;
class A_B: public A, public B{
};
class MyClass1: public A_B, public XXX{
};
class MyClass2: public A_B, public YYY{
};
void myFunc( A_B obj );
Я согласен, что это выглядит подозрительно очень похоже на Java.
Один из способов смягчить минусы шаблона
void myFunc_impl(A& aPart, B& bPart); // aPart and bPart are from the same object.
template <typename T>
void myFunc(T& obj) {
// static_assert to have better error messages
static_assert(std::is_base_of<A, T>::value, "T should inherit from A");
static_assert(std::is_base_of<B, T>::value, "T should inherit from B");
// Forwarding to implementation
myFunc_impl(obj, obj);
}
1) заставить определение находиться в заголовке, сложно поддерживать
У вас просто есть пересылка в шапке:
код короткий.
2) сложно управлять с помощью полиморфных указателей,
Пока вы берете ссылку или указатель, эта часть в порядке с шаблоном.
4) Это затрудняет или делает невозможным некоторые другие функции, такие как виртуальные?
Действительно, метод шаблона не может быть шаблоном, но здесь вы можете перейти к виртуальному методу.
5) Код трудно проверить, так как проблемы проявляются только на этапе использования.
Вы действительно должны создать его экземпляр, чтобы увидеть все ошибки.