class AbstractFoo {
virtual int do_something() = 0;
}
class Manipula {}
class Bar : public AbstractFoo, public Manipula {}
class Zap : public AbstractFoo, public Manipula {}
Понятно что все классы выше являются абстрактными.
Теперь нужно соорудить Concrete Bar и Zap добавив к ним имплементацию int do_something()
Наивный подход примерно такой:
#if WINDOWS
class AbstractFooImpl {
int do_something() { return 0; }
}
#elif MACOS
...
#endif
class ConcreteBar : public Bar, AbstractFooImpl {}
class ConcreteZap : public Zap, AbstractFooImpl {}
но он не работает, т.к. AbstractFoo::do_something() и AbstractFooImpl::do_something() есть разные сущности.
Каким образом это сделать более кошерно ?
Пока два варианта видится : virtual inheritance и #define простихоспидя. Что еще можно придумать?
Здравствуйте, c-smile, Вы писали:
CS>Есть вот такая иерархия классов CS>Понятно что все классы выше являются абстрактными. CS>Теперь нужно соорудить Concrete Bar и Zap добавив к ним имплементацию int do_something() CS>Наивный подход примерно такой: CS>но он не работает, т.к. AbstractFoo::do_something() и AbstractFooImpl::do_something() есть разные сущности. CS>Каким образом это сделать более кошерно ?
Я бы предложил переименовать как-нибудь AbstractFooImpl::do_something. А потом подмешивать AbstractFooImpl при помощи дополнительного шаблонного класса:
class AbstractFoo {
virtual int do_something() = 0;
};
class Manipula {};
class Bar : public AbstractFoo, public Manipula {};
class Zap : public AbstractFoo, public Manipula {};
#if WINDOWS
class AbstractFooImpl {
int do_something_impl() { return 0; }
};
#elif MACOS
...
#endif
template <typename Base>
class AbstractFooImplMixer : public Base, public AbstractFooImpl {
int do_something() override {
return AbstractFooImpl::do_something_impl();
}
};
class ConcreteBar : public AbstractFooImplMixer<Bar> { };
class ConcreteZap : public AbstractFooImplMixer<Zap> { };
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>Я бы предложил переименовать как-нибудь AbstractFooImpl::do_something. А потом подмешивать AbstractFooImpl при помощи дополнительного шаблонного класса:
А чем такой mixer лучше виртуального наследования:
class AbstractFoo {
virtual int do_something() = 0;
}
class Manipula {}
class Bar : public virtual AbstractFoo, public Manipula {}
class Zap : public virtual AbstractFoo, public Manipula {}
class ConcreteFoo : public virtual AbstractFoo {
int do_something() { return 0; }
}
class ConcreteBar : public Bar, ConcreteFoo {}
class ConcreteZap : public Zap, ConcreteFoo {}
Здравствуйте, c-smile, Вы писали:
CS>А чем такой mixer лучше виртуального наследования:
Просто как альтернативный вариант. Ромбовидное и/или виртуальное наследование имеет свои издержки и некоторые разработчики всеми способами избегают их использования. Как пример, можно почитать здесь.
--
Не можешь достичь желаемого — пожелай достигнутого.
// Original
// ---------------------------------class AbstractFoo
{
virtual int do_something() = 0;
};
class Manipula {};
class Bar : public AbstractFoo, public Manipula {};
class Zap : public AbstractFoo, public Manipula {};
// ---------------------------------#if 1
class WinFooImplZap : Zap
{
virtual int do_something() override { return 0; }
};
#endif
class ConcreteZap : public WinFooImplZap
{
// we inherit impl here from WinFooImplZap
};
Здравствуйте, Engler, Вы писали:
E>А если вот так?
E>
E>// Original
E>// ---------------------------------
E>class AbstractFoo
E>{
E> virtual int do_something() = 0;
E>};
E>class Manipula {};
E>class Bar : public AbstractFoo, public Manipula {};
E>class Zap : public AbstractFoo, public Manipula {};
E>// ---------------------------------
E>#if 1
E>class WinFooImplZap : Zap
E>{
E> virtual int do_something() override { return 0; }
E>};
E>#endif
E>class ConcreteZap : public WinFooImplZap
E>{
E> // we inherit impl here from WinFooImplZap
E>};
E>
С одним классом понятно, но в моем примере их два (а в реале больше).
Смысл в том чтобы иметь одну имплементацию ConcreteFoo и добавлять её (mixin) к нужным классам.
Здравствуйте, c-smile, Вы писали:
CS>С одним классом понятно, но в моем примере их два (а в реале больше). CS>Смысл в том чтобы иметь одну имплементацию ConcreteFoo и добавлять её (mixin) к нужным классам.
так а сделай этот один класс шаблонным параметром и указывай явно при описании Concrete?
#include <iostream>
using namespace std;
class AbstractFoo {
public:
virtual int do_something() = 0;
};
class Manipula {};
class Bar : public AbstractFoo, public Manipula {};
class Zap : public AbstractFoo, public Manipula {};
#if 1
template <class Base>
class AbstractFooImpl : public Base {
int do_something() { return 0; }
};
#endif
class ConcreteBar : public AbstractFooImpl<Bar> {};
class ConcreteZap : public AbstractFooImpl<Zap> {};
int main() {
AbstractFoo *af;
af = new ConcreteBar;
af->do_something();
af = new ConcreteZap;
af->do_something();
return 0;
}
Здравствуйте, Аноним, Вы писали:
А>так а сделай этот один класс шаблонным параметром и указывай явно при описании Concrete?
Ситуация реально там несколько более закрученная.
Это вот не компилируется (есс-но):
class iwindow {
public:
int n;
iwindow() : n(42) {}
virtual bool foo() = 0;
};
template<typename Base>
class iwindow_impl: public Base {
public:
virtual bool foo() override { return true; }
};
class a : public iwindow {
};
class b : public a, public iwindow_impl<b> {
public:
b() { n = 44; }
};
int main()
{
b* binst = new b();
printf("b %d %d\n", binst->foo(), sizeof(b) );
return 0;
}
Нужно доп. прослойка:
class iwindow {
public:
int n;
iwindow() : n(42) {}
virtual bool foo() = 0;
};
template<typename Base>
class iwindow_impl: public Base {
public:
virtual bool foo() override { return true; }
};
class a : public iwindow {
};
class a_bis : public a {
public:
a_bis() { n = 44; }
};
typedef iwindow_impl<a_bis> b;
int main()
{
b* binst = new b();
printf("b %d %d\n", binst->foo(), sizeof(b) );
return 0;
}
Здравствуйте, rean, Вы писали:
R>Прежде чем добавлять универсальный абстрактный класс, зависимый от платформенных фичь, лучше много раз подумать, прежде чем так делать.
Ага, спасибо.
R>Предлагаю сместить абстракцию ближе к клиентскому коду. R>Например, что-то типа: R>[ccode] R>class UniversalFeatures { R>#ifdef _WIN32 R> WindowsFeatures R>#else R> MacOSFeatures R>#endif R> features; R> ...
Не получится. Разные файлы .mm (MacOS Objective-C++) и .cpp для остальных.
R>Только не virtual. Это не решит задачу разницу между платформами.
Там не только между платформами. Например для Linux поддерживать три типа window backend: GTK/Gnome, KDE/Qt, XWindow... и все это в одном binary.
R>PS. Множественное наследование — зло. Лучше юзеру его не давать, внутри себя можно, но в классах, какие никогда не будут видны пользователю.
Здравствуйте, c-smile, Вы писали:
CS>Пока два варианта видится : virtual inheritance и #define простихоспидя. Что еще можно придумать?
Можно через CRTP протащить тип на почти самый верх и там звать переименованный FooImpl. (Это немного отличается от уже предложенных шаблонных вариантов).
#include <cstdio>
class AbstractFoo {
virtual int do_something() = 0;
};
template<typename T>
class AbstractFooFwd: public AbstractFoo
{
public:
virtual int do_something()
{
return static_cast<T*>( this )->do_something_impl();
}
};
class Manipula {};
template<typename T>
class Bar : public AbstractFooFwd<T>, public Manipula {};
template<typename T>
class Zap : public AbstractFooFwd<T>, public Manipula {};
class AbstractFooImpl {
public:
int do_something_impl() { return 42; }
};
class ConcreteBar : public Bar<ConcreteBar>, public AbstractFooImpl {};
class ConcreteZap : public Zap<ConcreteZap>, public AbstractFooImpl {};
int main(int argc, char* argv[])
{
ConcreteBar a;
ConcreteZap b;
printf("%d %d\n", a.do_something(), b.do_something());
return 0;
}
Еще вариант — увеличить размер класса и хранить явный указатель на impl через еще один интерфейс, но там все равно CRTP и в итоге получается даже хуже.
В общем, если не пересматривать структуру наследования, то виртуально наследование — самый простой вариант.
Здравствуйте, rean, Вы писали:
CS>>Там не только между платформами. Например для Linux поддерживать три типа window backend: GTK/Gnome, KDE/Qt, XWindow... и все это в одном binary.
R>А как собираетесь решать проблему отсутствия требумых dll? Оно же не запустится, если не будет чего-то на машине.
Ну дык dlopen / LoadLibrary же ж... Т.е. lazy load.
Что есть не том и работаем ...
Ну вот Sciter например работает от WinXP до Win10 — binaries одни и те же.
При этом на Win10 используются некоторые функции которые только там и есть.
Здравствуйте, c-smile, Вы писали:
CS>Каким образом это сделать более кошерно ?
CS>Пока два варианта видится : virtual inheritance и #define простихоспидя. Что еще можно придумать?
однажды поднял этот вопрос, нормольно не решается.
Самое чистое решение вот такое:
class A
{
public:
virtual ~A(){}
template<class... ParamsT>
inline auto foo(ParamsT&&...params)
{
return foo_A_impl(std::forward<ParamsT>(params)...);
}
protected:
virtual void foo_A_impl(int) = 0;
};
class B
{
public:
virtual ~B(){}
template<class... ParamsT>
inline auto foo(ParamsT&&...params)
{
return foo_B_impl(std::forward<ParamsT>(params)...);
}
protected:
virtual void foo_B_impl(int) = 0;
};
class C : public A, public B
{
protected:
virtual void foo_A_impl(int) override
{
//do something as A
}
virtual void foo_B_impl(int) override
{
//do something as B
}
};