Здравствуйте, mjau, Вы писали:
M>Ааээ а чем это отличается от просто виртуальных функций? В наследниках тоже будут свои реализации foo() и bar() и их тоже надо будет ифдефить. На оставленные без деклараций тела компилятор будет ругаться. В чем выигрыш?
Отличается тем, что, допустим, тебе эти foo(), bar() для чего-то ещё нужны, сами по себе.
И заодно, через них выражается реализация B().
Выигрыш в том, что B() ты убираешь малой кровью.
К>>// а эта на CRTP
M>А CRTP работает дальше одного наследника? А как?
У классов с виртуальными функциями в рантайме в объекте хранится vfptr на актуальную vtbl, а у CRTP — в шаблон класса протаскивается параметр — финальный класс.
Единственная сложность — это то, что нефинальные классы должны быть шаблонами.
template<class TActual>
class Base
{
public:
TActual & self() { return *static_cast<TActual *>(this); }
TActual const& self() const { return *static_cast<TActual const*>(this); }
// паттерн Шаблонный Метод: "виртуально" вызываем методы наследникаvoid foo()
{
self().bar();
self().buz();
}
void bar() {} // "виртуальная" функция, которую наследник может переопределить, перегрузив
//void buz(); // "чисто виртуальная" функция - наследник обязан её определить
};
template<class TActual>
class Intermediate : public Base<TActual>
{
void bar() {}
void buz() {}
};
class Final : public Intermediate<Final>
{
void bar() {}
};
CRTP вовсю используется в ATL и WTL. Причём используется довольно стройно и последовательно. Так что можешь познакомиться с этой идиомой практически.
class A
{
virtual void A() = 0;
virtual void B() = 0;
...
}
и пучок наследников от него, соответственно с различными реализациями A() и B().
Хочется использовать эти классы в нескольких применениях (программах), причем в одном востребованы как A() так и B(), а в другом B() не используется и компилировать все реализации метода особого смысла нет. Есть ли какой-нибудь более человеческий способ исключения B() из компиляции, кроме
— окружения всех деклараций и реализаций B() в потомках кучкой #ifdef COMPILE_B
— вынесения всех реализаций B() в отдельный файл и его подключения/отключения?
Каждый, просыпаясь утром, должен задавать себе вопрос — что он может сегодня сделать, чтобы россиянства
Здравствуйте, mjau, Вы писали:
M>Хочется использовать эти классы в нескольких применениях (программах), причем в одном востребованы как A() так и B(), а в другом B() не используется и компилировать все реализации метода особого смысла нет. Есть ли какой-нибудь более человеческий способ исключения B() из компиляции, кроме M>- окружения всех деклараций и реализаций B() в потомках кучкой #ifdef COMPILE_B M>- вынесения всех реализаций B() в отдельный файл и его подключения/отключения?
Можно условно компилировать интерфейс с и без этой функции B(), а в наследниках не писать virtual. Тогда, если B() в интерфейсе отсутствует, то у наследников они будут невиртуальными функциями, нигде не использующимися (да?), и линкер их выкинет.
Ну а чтоб и компилятор не перетруждать — тут уж только ифдефить повсеместно.
Либо как-то выразить B() единообразно через другие члены наследника (т.е. воплотить паттерн Шаблонный Метод), и соответственно, ифдефить B() в промежуточном базовом классе.
Причём шаблонный метод можно сделать как на виртуальных функциях, так и на CRTP. Это уже нюансы.
// небольшое количество единообразных реализаций...
// эта пусть будет на виртуальных функцияхclass CommonImpl1 : public IA
{
#ifdef COMPILE_B
private: // ибо незачем наследникам вызывать то, что может быть исключено из компиляцииvirtual void B() { this->foo(); this->bar(); }
protected:
virtual void foo();
virtual void bar();
#endif
};
// а эта на CRTPtemplate<class T>
class CommonImpl2 : public IA
{
#ifdef COMPILE_B
private:
virtual void B() { static_cast<T*>(this)->buz(); }
};
// и куча окончательных наследниковclass Derived1 : public CommonImpl1 { ..... };
class Derived2 : public CommonImpl1 { ..... };
class Derived3 : public CommonImpl2<Derived3> { ..... };
template<class T> class Derived4 : public CommonImpl2<T> { ..... }; // аналог виртуальности для CRTP :)
Здравствуйте, Кодт, Вы писали:
К>Ну а чтоб и компилятор не перетруждать — тут уж только ифдефить повсеместно. К>Либо как-то выразить B() единообразно через другие члены наследника (т.е. воплотить паттерн Шаблонный Метод), и соответственно, ифдефить B() в промежуточном базовом классе. К>Причём шаблонный метод можно сделать как на виртуальных функциях, так и на CRTP. Это уже нюансы.
К>
К>// эта пусть будет на виртуальных функциях
К>class CommonImpl1 : public IA
К>{
К>#ifdef COMPILE_B
К>private: // ибо незачем наследникам вызывать то, что может быть исключено из компиляции
К> virtual void B() { this->foo(); this->bar(); }
К>protected:
К> virtual void foo();
К> virtual void bar();
К>#endif
К>};
Ааээ а чем это отличается от просто виртуальных функций? В наследниках тоже будут свои реализации foo() и bar() и их тоже надо будет ифдефить. На оставленные без деклараций тела компилятор будет ругаться. В чем выигрыш?
К>// а эта на CRTP
А CRTP работает дальше одного наследника? А как?
class IA{..}
template< class T >
class IB
{
public:
#ifdef USE_B
void b()
{
static_cast<T*>(this)->b_impl();
}
#endif
};
class Base : public IA, public IB<A>
{
public:
void b_impl(){...};
};
class B: public Base, public IB<B> (?)
{
public:
void b_impl(){...};
};
int main()
{
Base *ca = new Base();
Base *cb = new B();
#ifdef USE_B
ca->b(); // <-- Base::b_impl
cb->b(); // <-- тоже Base::b_impl#endif
Каждый, просыпаясь утром, должен задавать себе вопрос — что он может сегодня сделать, чтобы россиянства
Здравствуйте, mjau, Вы писали:
M>Хочется использовать эти классы в нескольких применениях (программах), причем в одном востребованы как A() так и B(), а в другом B() не используется и компилировать все реализации метода особого смысла нет. Есть ли какой-нибудь более человеческий способ исключения B() из компиляции, кроме M>- окружения всех деклараций и реализаций B() в потомках кучкой #ifdef COMPILE_B M>- вынесения всех реализаций B() в отдельный файл и его подключения/отключения?
А чего ты хочешь добиться? Уменьшения времени компиляции? Уменьшения размера кода? Чего-то ещё?
Просто, если речь идёт о первом, то обычно это решают при помощи вынесения такой штуки в lib, а если о втором, то не совсем понятно, как планируется применять второй способ.
Кроме того, у тебя, IMHO, возникнет ещё одна проблема -- клиентский код этого интерфейса, может начать разделяться между разными приложениями, и там тоже надо будет как-то управылять тем, какие именно методы доступны...
Я бы, например, порубил твоё интерфейс на более мелкие, типа InterfacrWithA и InterfacrWithB, а в твоём интерфейсе имел бы только InterfacrWithA* GetA() и InterfacrWithA* GetB().
В клиентском коде переключался бы по тому, что вернул Get (0 или интерфейс), а в реализации включал бы или нет нужную базу...
Если речь идёт о пучке наследников, то сделал бы какой-то механизм вколючать/не включать сразщу весь пучёк.
Но это уже сильно зависит насколько тесно взаимодействуют реализации того и другого интерфейса.
Например, если совсем не взаимодействуют, то можно было бы породить несколько независимых пучков реализаций, и какую-о систему статических регистрилок. Ну то есть если, скажем, у тебя был пучёк наследников Impl1, Impl2, Impl3 и т. д. То теперь у тебя будет ImplA1, Implb1, ImplA2, ImplB2 и т. д. И что-то типа
// ImplA2.cpp
// реализация класса ImplA2static CImplRegistrar<Impl2, ImplA2> regIt;
Тогда просто включая соответсвующие cpp в проект ты будешь фактически динамически конфигурить всю систему...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском