Re[3]: модификация интерфейса в compile time
От: Кодт Россия  
Дата: 08.01.09 11:34
Оценка: 1 (1)
Здравствуйте, 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. Причём используется довольно стройно и последовательно. Так что можешь познакомиться с этой идиомой практически.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
модификация интерфейса в compile time
От: mjau  
Дата: 06.01.09 14:54
Оценка:
в библиотеке есть некий интерфейс типа
class A
{
    virtual void A() = 0;
    virtual void B() = 0;
    ...
}

и пучок наследников от него, соответственно с различными реализациями A() и B().
Хочется использовать эти классы в нескольких применениях (программах), причем в одном востребованы как A() так и B(), а в другом B() не используется и компилировать все реализации метода особого смысла нет. Есть ли какой-нибудь более человеческий способ исключения B() из компиляции, кроме
— окружения всех деклараций и реализаций B() в потомках кучкой #ifdef COMPILE_B
— вынесения всех реализаций B() в отдельный файл и его подключения/отключения?
Каждый, просыпаясь утром, должен задавать себе вопрос — что он может сегодня сделать, чтобы россиянства
Автор: Kerk
Дата: 21.08.22
в мире стало меньше.
Re: модификация интерфейса в compile time
От: Кодт Россия  
Дата: 06.01.09 16:23
Оценка:
Здравствуйте, 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
};

// а эта на CRTP
template<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 :)
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re[2]: модификация интерфейса в compile time
От: mjau  
Дата: 07.01.09 13:30
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Ну а чтоб и компилятор не перетруждать — тут уж только ифдефить повсеместно.

К>Либо как-то выразить 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
Каждый, просыпаясь утром, должен задавать себе вопрос — что он может сегодня сделать, чтобы россиянства
Автор: Kerk
Дата: 21.08.22
в мире стало меньше.
Re: модификация интерфейса в compile time
От: Erop Россия  
Дата: 09.01.09 03:36
Оценка:
Здравствуйте, 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
//  реализация класса ImplA2

static CImplRegistrar<Impl2, ImplA2> regIt;

Тогда просто включая соответсвующие cpp в проект ты будешь фактически динамически конфигурить всю систему...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.