Иногда, скорость - это все. В таких случаях приходится избавляться от всего замедляющего работу. Бывает, что приходится отказываться и от элегантности архитектуры. Именно так бывает если приходишь к выводу, что для ускорения необходимо избавиться от таблицы виртуальных функций.
Предлагаю Вашему вниманию способ "виртуальных функций на этапе компиляции". Этот подход не перекрывает всю функциональность виртуальных функций, но часто его возможностей достаточно.
Итак код:
template <class T> class B
{
public:
void Hello()
{
T* pT = static_cast<T*>(this);
pT->MyName();
}
void MyName() { cout << "This is B"; }
};

class D1 : public B<D1>
{
};

class D2 : public B<D2>
{
void MyName() { cout << "This is D2"; }
};

int main()
{
D1 d1;
D2 d2;

d1.Hello(); //"This is B"
d2.Hello(); //"This is D2"

return 0;
}
Тут "виртуальной" является функция MyName. Весь фокус состоит в том, что при объявлении производного класса базовому классу передается тип нового класса в качестве параметра шаблона. А базовый класс при вызове "виртуальных" функций преобразовывает this к необходимому типу.
Думаю многих смущает конструкция вида "class D2 : public B {...};". Но люди, вкусившие элегантность С++, сразу вспомнять что идентификатор класса считается объявленным сразу после "class D2", а следовательно может быть использован в списке наследования.

Удачной оптимизации :)

google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru

24 Комментариев на ““Виртуальные функции” без таблицы виртуальных функций”

  1. zeroreturn сказал:

    Насколько данная идиома применима в диамантовым иерархиям, например:

    class Base {};
    class ChildLeft : virtual public Base {};
    class ChildRight: virtual public Base {};
    class Child: public ChildLeft, public ChildRight {};

    Как себя она ведет при различных вариантах множественного наследования? Каким образом можно вынести данную идиому до уровня паттерна и можно ли вообще?

  2. seagull сказал:

    Я использовал данный подход для неперекрестного множественного наследования.
    Как использовать для приведенного примера? Незнаю, поэкспериментируй.
    Насчет, паттерна. Если для тебя, Дима, так важно обозвать подход паттерном - называй его так :) Не забывай о первоначальном понятии слова паттерн - это не обязательно базовый класс, добавляющий функциональность.

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

  3. Roman Movchan сказал:

    я читал.

    понял мало :(

    как говорится, мне б ваши проблемы :)

  4. seagull сказал:

    Решил добавить.
    Если кто решит как применить подобный подход для указанной Димой иерархии, милости просим написать в комментариях. Было бы интересно.

  5. usix сказал:

    ох. а что это за случаи, когда скорость доступа к методам настолько существенна? ;)

  6. seagull сказал:

    2usix:
    например, нейронная сеть с миллионами элементов и нетривиальной структурой (т.е. обязательна реализация "каждому нейрону свой объект"). И для каждого из нейронов вызывается набор методов.
    Вот и посчитай :)

  7. GLad сказал:

    Ну вот, заговорили о высоком :) Да, плюсы рулят, по ходу зе бест :)

  8. doc сказал:

    Имхо дешевле купить Deep Blue, чем писать что-то серьозное в таком стиле.

  9. seagull сказал:

    doc, одно к другому не относиться.

    Если бы все так мыслили, то нафиг вообще алгоритмы. Ведь все можно перебором решить :)

  10. usix сказал:

    2seagull:
    миллион объектов? кошмар! остается только догадываться, какие мысли зародятся в нейронной сети, реализованной таким образом ;)

    ЗЫ: может использовать по flyweight'у на каждый поток? можно с кодогенерацией...

  11. seagull сказал:

    да уж это мыслящий монстер :)))

    А можно поподробней по поводу flyweight?

  12. zeroreturn сказал:

    наверное имелся ввиду паттерн проектирования
    http://en.wikipedia.org/wiki/Flyweight_pattern

  13. seagull сказал:

    Паттерн полезный, но не в данном случае. Но, все равно спасибо за то что немного образовали меня, добавив новое модное слово на старое понятие.
    Хотя хотелось бы заметить, что "песня совсем не о том" - обсуждаем мы не реализацию НС, а предложенный подход оптимизации.

  14. kolobok сказал:

    Я тоже довольно много задаюсь вопросом о ускорении работы своих программ, поэтому я избегаю использования виртуальных функций. Но один вопрос меня действительно сильно волнует: влияет ли на скорость наследование ( без виртуальных функций )? Если влияет, то насколько?

  15. Руслан Пилин сказал:

    Я вот о чем подумал. Может нам ради производительности вообще отказаться от ООП и писать на ASM? А то пишем на каких-то тормознутых Java, Ruby, Perl, Python, PHP, C, C++, .NET, SmallTalk (нужное подчеркнуть). Что нам дают более абстаркные языки? Какие, например, преимущества у SmallTalk против ASM(C, C++)?

  16. Руслан Пилин сказал:

    Вопрос к коду. Насколько я понял мы виртуальные функции перегружаем на этапе компиляции. Правильно?

  17. seagull сказал:

    2 Руслан:
    Как по мне языки высокого уровня дают нам скорость разработки, а это уже много. Но бывают таки участки кода, где стоит "спуститься", пожертвовав "высоким и красивым" в пользу эффективности.

    >Вопрос к коду. Насколько я понял мы виртуальные
    >функции перегружаем на этапе компиляции. Правильно?
    По сути правильно, но не совсем. Немного остается работы и на run-time.

    2 kolobok:
    >влияет ли на скорость наследование ( без виртуальных
    >функций )? Если влияет, то насколько?
    Сложный вопрос. Не совсем ясно, что подразумевается под влиянием наследствения на скорость.
    Я так понимаю у вас есть код который можно написать в рамках ООП или без ООП. так? Тогда напишите 2 таких кода и замеряйте время выполнения. Будет интересно почитать вашу статью по этому поводу.

  18. zeroreturn сказал:

    >> kolobok
    Если тебе настолько важна скорость, отпрофилируй код и перепиши critical sections на ASM вставки. В пограничных случаях можно использовать замещение, вместо более громоздкого наследования с RTTI. Хотя на практике, проблеммы недостаточной производилельности системы -- это следствие недостроенной архитектуры. Как вариант можно смоделировать наследование функторами.

  19. zeroreturn сказал:

    >Вопрос к коду. Насколько я понял мы виртуальные
    >функции перегружаем на этапе компиляции. Правильно?
    По сути правильно, но не совсем. Немного остается работы и на run-time.

    тут Виталик схитрил :) на самом деле действительно все делается на этапе компиляции: при инстранцировании шаблона в фукнции Hello будет происходить передача управления на замещенную фукнцию MyHello в результирующем классе T. В runtime это будет выглядеть просто как вызов обычной фукнции (как раз это наверное и имел ввиду Виталик :) ).

  20. Руслан Пилин сказал:

    Где-то попадала интересная ссылка на преимущества лисп. Сколько операций нужно(можно) оптимизировать в компиляторе С++, что бы получить выиграш по скорости?
    Сколько операций нужно(можно) в Self, для увеличения скорости?

    И еще на закуску. Интересный факт. Извесно, что узкое место многих сложных бизнес систем - вызов полиморфного метода. Второй интересный факт: эта операция работает в Ява быстрее, чем в С++. Поразительно, но факт. К сожалению не могу привести ссылки на первоисточник. (Статья про SmallTalk и Self) Про рефлексию, AOP и DynamicProxy я вообще молчу.

  21. seagull сказал:

    2 Дима:
    Ничего я не хитрил :))) Просто в runtime выполняется излишний static_cast + выделение места под временный указатель + присвоение :) - все это тоже займет процессорное время, хотя возможно современные компиляторы это дело и прооптимизируют.
    Но остается очевидным что такие растраты по сравнению с работой виртуальной функции - мизерны. Видимо, Дима решил их не принимать во внимание. Что же эти моментами можно пренебресь вполне обоснованно ;)

    2 Руслан:
    Все что ты написал очень заинтересовало. Большая просьба найти первоисточники.

    и еще раз к Диме:
    >Как вариант можно смоделировать наследование
    >функторами.
    Звучить сюрреалистично для нас незнающих, хотя и радует слух человека любящего математику :)
    Можно поподробней.

  22. zeroreturn сказал:

    Виталик, что бы не быть голословным по поводу того, что static_cast никаких действий в runtime не влечет: http://www.cppreference.com/keywords/static_cast.html, http://www.codeproject.com/cpp/static_cast.asp.

  23. seagull сказал:

    Но еще остается:
    >+ выделение места под временный указатель + присвоение

    Хотя это можно перебороть инлайн функцией:

    T* getthis()
    {
    return static_cast<T*>(this);
    }

    А потом использовать так:
    getthis()->MyName();

    Вот теперь мы точно перенесли все на время компиляции.

    И еще раз повторюсь: Дима просвети по поводу функторов.

  24. zeroreturn сказал:

    http://ckdev.org.ua/2006/12/15/modelirovanie-virtualnyih-funktsiy-funktorami/





Оставте свое мнение