+155
- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
#include <iostream>
#include <memory>
#include <assert.h>
using namespace std;
template <class T>
class megaClass
{
public:
void hello()
{
assert(dynamic_cast<T*>(this)!=NULL);
static_cast<T*>(this)->hello();
}
virtual ~megaClass() {}
};
class cleft : public megaClass<cleft>
{
public:
void hello()
{
std::cout << "left::hello()" << std::endl;
}
};
class cright : public megaClass<cright>
{
public:
void hello()
{
std::cout << "right::hello()" << std::endl;
}
};
int main()
{
scoped_ptr<megaClass<cleft> > a1=new cleft;
a1->hello();
scoped_ptr<megaClass<cright> > a2=new cright;
a2->hello();
return 0;
}
Пытался продемонстрировать статический полиморфизм TarasB и получилась такая какашка. Кто действительно крут и может сабдж продемонстрировать? Я где-то видел пример, но не помню где...
Ещё продемонстрировал статический полиморфизм через стратегии:
struct Strategy1
{
static void do(){printf("Lol1");}
};
struct Strategy2
{
static void do(){printf("Lol2");}
};
template<class Strategy>
class MegaClass
{public:
void do()
{
printf("Mega");
Strategy::do();//Класс Strategy можно было и создать для хранения состояния.
printf("/n\");
}
};
//...
Дальше в разных частях кода создаем:
MegaClass<Strategy1> o;
o.do();
//...
MegaClass<Strategy2> o;
o.do();
"Один" класс ведёт себя по разному. Понятно, что это не совсем полиморфизм. Но очень часто именно в таком контексте используют динамический полиморфизм, хотя такого статического здесь достаточно выше крыши.
Плюсы этого подхода :
1)Создаётся объект в стеке, значит быстро, а не в куче. Хотя можно и не в стеке.
2)Используется шаблон, значит компиль будет инлайнить.
Минус:
1)Если понадобится резкой перейти от статического полиморфизма к динамическому - придётся переписывать на виртуальные функции или на истинный статический полиморфизм.
Обсуждения здесь:
http://govnokod.ru/8025#comment110773
Сразу исключим детсадовский вариант статического функционального полиморфизма c перегрузкой функций:Class1 o1;
foo(o1);
Class2 o2;
foo(o2);
void foo(Class1 o){/*...*/};
void foo(Class2 o){/*...*/};
Кто-нибудь реально умеет can into нормальный статический полиморфизм?
Запостил:
CPPGovno,
30 Сентября 2011
Почему детсадовский? Если несколько копий функции будет писать не программист, а компилятор, то почему бы и нет?
у тебя ран-тайм полиморфизм
done
>>2)Используется шаблон, значит компиль будет инлайнить.
А в результате - приведение типов через reinterpret_cast, нельзя создать массив полиморфных объектов без е..ли и вообще грязь в коде. Смысл?
вызовы функций через указатели не инлайнятся. более того, если на функцию создатся хотя бы один указатель, она вообще не может инлайнится
-точно, а я то думаю, кого он мне напоминает
-но зато он самый быстрый, сильный и гибкий
но чуть ты оплашаешь - обязательно упадешь на большой скорости вниз под копыта
приготовься и запасись терпением. пройдет много времени, прежде чем он тебя зауважает и ты почувствуешь себя его хозяином
анон, ты немного уныл, вернись на сосач
См Static polymorphism в
для виртуальных функций:
первый указатель: указатель на объект (он есть, даже если объект создан в стеке)
второй указатель на VTBL (таблицу виртуальных методов)
третий указатель на виртуальный метод
для не виртуальных методов:
тупо вызывается метод, с зафиксированным константным адресом в опкоде комманды call, что намного быстрее для вызова, даже чем через обычный 1 указатель.
Меня ещё смущает как удалось вызвать приватный метод.
впрочему похуй. кто в теме и так знает, а кто не в теме, тот не поймет нихуя
По-моему мнению, объяснить вы не смогли, ни простыми, ни сложными словами.
И да, Pimpl — это немного не то.
Можно придумать и другие способы. Например, в виртуальной таблице хранить не указатели, а сами методы (в элементах фиксированного размера). Небольшие поместятся все, для больших — команда перехода (или начало и команда перехода). Увеличение расхода памяти не зависит от числа объектов, и на один уровень косвенности для малых методов меньше (для которых это имеет смысл).
Можно избавиться от указателя на таблицу, если объекты разного типа распределять в разных областях памяти. Тогда тип однозначно определяется указателем (старшими битами). В принципе, это реально организовать даже на современных 64-битных процессорах. С стековыми объектами придётся повозиться (по сути на стеке это будут только прозрачные умные указатели), этот способ больше подходит для Java или C#.
Это понятно. Только вот на си этот метод не к месту. Если пишешь для небольших контроллеров - каждый байт на счету. А тут растраты на пустом месте...
Не имеет смысла усложнять на один процент из-за почти отсутствующей выгоды.
>Если в некоторой иерархии всего один виртуальный метод и не используется rtti, то оверхеда нет и это бесспорная оптимизация.
Ох какая бесспорная... Виртуальных методов обычно или 0 или >=2. Случая с одним просто нет. Обычно, если появляется хоть один метод, то сразу появляется и виртуальный деструктор. А rtti вообще по стандарту языка должен быть, если есть виртуальные методы. А rtti это ещё один указатель\виртуальный метод.
RTTI должен быть, если его используют. А если нет для какой-то иерархии dynamic_cast или typeid, то и не нужен.
Эту оптимизацию и вручную сделать можно, указателем на функцию, но если компилятор сам заметит, приятно.
Если хоть в одном классе данной иерархии он используется, то его придется использовать везде, иначе кастить не получится этот объект.
Если хотя бы в одном классе данной иерархии 2 виртуальные функции, то придется заводить указатель на таблицу виртуальных функций во всей иерархии, иначе кастить также не получится.
А с учетом современных тенденций: один класс предок на всю иерархию программы, например как QObject в QT или System.Object в C#, то эта оптимизация совсем работать не будет.
Сила текущей организации vtbl в объекте в том, что это наиболее эффективное решение, но при этом универсальное, поэтому компилятор очень прост не обременен анализом мелких редких частных случаев, не дающим особого выигрыша в реальных условиях.
А между тем компилятор не сможет оценить всю иерархию классов, тк часть классов используется в одной единице трансляций, а часть в другой. А это уже медленный глобальный анализ всего кода всех obj файлов. Линкер повесится, не смотря на то, что это не его работа.
Вопрос межмодульного анализа и оптимизации до сих пор почти не решен ни в одном языке., тк это пока не по зубам современным компьютерам из-за ограничения производительности.
Если вам что-то не нравится, то напишите свой компилятор или вы знаете, где найти assembler и ansi C для экономии на редко встречающихся спичках в своих программах.
Некоторые способы оптимизации (например, dynamic_cast за O(1)) требуют знание о всей иерархии классов. Сейчас компилятор и редактор связей намного умнее, чем были 30 лет назад.
Я - нет, но QT все время. Выигрыш от метода теряется полностью на всех классах QT приложения.
>Сейчас компилятор и редактор связей намного умнее
Вот только не надо. Вижу я этот "ум и гениальность" межмодульной оптимизации каждый день, особенно в GCC или MSVC... И это при часовых компиляциях. А что будет, если добавить ещё больше "ума" межмодульному оптимизатору?
Не подходит С++ для нормальной межмодульной оптимизации., особенно с его препроцессором и включаемыми файлами. 2 этих фактора нарушают легкость межмодульной оптимизации. Компилятору приходится генерировать код, не нарушающий ODR (я про ODR машинного уровня, а не уровня кода), что не так просто, если сделать его "сильно умным" и при этом ошибиться хоть где-нибудь.
Ну и говорить нечего тогда. За чем тогда в спор ввязались? С этим не сталкивались... Этого не знаете... Этого не умеете... и тд...
>Да вы можете использовать классы вообще без виртуальных методов
Спасибо, за разрешение.
Зато есть стандарт де факто и его также желательно поддерживать. Иначе под туже винду нельзя будет легко писать, ибо там COM в моде, особенно с выходом "ЭпикВин8".
Вообщем, терять совместимость из-за копеечных сомнительных оптимизаций при усложнении компиля - не вариант.
Может для вас это секрет, но все компиляторы до конца не совместимы со стандартом.
>Всё равно ведь для того, чтобы сделать COM, нужны дополнительные действия
На GCC или ANSI C в легкую делается COM не выходя за пределы языка.
>Может быть нужны будут специальные подсказки компилятору (прагмы),
Костылей и так хватает. Нечего добавлять новые.
Все уже продумано и во всю используется. Почитайте хотя бы про COM, ABI, что б хоть представлять что это такое. Я уже говорил про стандарт де факто, который вы так хотите нарушить.
Кстати, оно в отрицательном смещении таблицы VTBL.
Не кэшфрендли.
Ещё один недостаток — увеличивается время создания объекта (нужно проинициализировать все указатели на методы).
Тогда и предлагать нечего.
>И продолжает использоваться и поддерживаться сейчас.
примеры?
Я и так знаю, что он использовался при царе горохе. Но это не серьёзно. Не зря же пришли к VTBL.
Что за капитанство? Qtшные классы во внутреннем устройстве не имеют никакого то отклонения от стандарта де факто, использующего VTBL. Все компилится и обычным С++ компилем + кодогенератором препроцессорного типа. Ничего менять в устройстве класса из-за QT не нужно и никто этого не делает.
>это так есть.
Я ещё раз спрашиваю примеры?
>Полагаю
Нечего полагать. Я жду фактов или объяснения большей оптимальности или необходимости такого метода.
Компилируется Qt не обычным компилятором C++, используются свои расширения языка, которые уже компилируются в C++. Напомню, что первый компилятор C++ компилировал в C.
Это проблемы этой библиотеки. К реализации виртуальных методов в языке - GUI-библиотеки не имеют никакого отношения. И если вы и говорите, что эта реализация медленная, так зачем предлагать её в качестве оптимизации для компилятора С++?
>Компилируется Qt не обычным компилятором C++
Я уже сказал, что QT проги компилируется обычным компилятором С++, предварительно прогнав код через кодогенератор препроцессорного типа. Теперь понятно, почему аргументы не доходят до вас и вы продолжаете без конца повторять одно и тоже. Если вы не внимательно читаете, то я не могу ничем вам помочь. Я не могу читать за вас. Видимо, это сигнал мне закончить разговор с вами.
Это вообще не слабое разбухание кода для очень разветвленных иерархий. Придется на каждый отдельный класс хранить свою таблицу кода абсолютно одинаковых функций.
А таких классов может быть много, не забывайте обо всех потомках и предках этого класса в иерархии с перекрывающими методами. А это много классов с абсолютно одинаковыми копиями методов. Особенно эпичный фейл ждет, если вспомнить современные тенденции к кодогенерации и шаблонам, то это уже будет нереально много классов с абсолютно одинаковыми копиями методов.
И вообще это не кешфрендли.
Хватит предлагать методы, что не подходят современным реалиям, а использовались разве что отцами.
Кстати, о кеше. Если разделять кеш для данных и кода, то код пары десятков классов осядет в кеше, а вот vtable с указателями будет постоянно выталкиваться из него обрабатываемыми данными. Но это частности.
А может быть, да кабы, да во рту выросли грибы. Из-за кодогенерации и шаблононов С++ классов очень много. Не хочу иметь дубликат всех функций map для каждой комбинации его шаблонных параметров. Проект и так долго компилируется, а тут ещё и кучу дубликатов. Полумеры не рулят. Ещё этот метод заставляет вставлять лишние джампы в конце каждой большой функции из таблицы, что также не кешфрендли.
>а вот vtable с указателями будет постоянно выталкиваться из него обрабатываемыми данными
Да ладно? Обычно виртуальные функции используются часто, так что не будет выталкиваться.
>от 32 до сотни байт, а самих объектов — миллионы
Эта задача - ещё один сферический кот в вакууме, которых вы так любите приводить в качестве примеров.
>Если компилятор, пошуршав несколько часов, выдаст бинарник, который просчитывает задачу на 20% быстрее, — честь ему и хвала.
Чтобы получить приличный прирост к производительности - нужно оптимизировать алгоритм, а не экономить на спичках (на инструкциях). Компилятор за вас говноалгоритм не перепишет. Если же вам нужна экономия на спичках, я уже сказал, где её найти (Си\асм).
Джамп (только не в конце, а в начале-середине) больших функций как раз соответствует текущей модели vtable.
Ну что поделать, вот с такими сферическими конями приходится иметь дело. Это комы да дотнеты для меня экзотика.
Писать такое на Си или, тем более, ассемблере — безумие, код засорится и помешает высокоуровневой оптимизации. А вот если компилятор бесплатно и прозрачно её проведёт, то можно на такие детали не обращать внимания и заняться алгоритмами.
Поэтому и не было смысла вводить эту оптимизацию. Если вспомнить все вышеперечисленные её недостатки.
>std::map не имеет виртуальных методов.
Ага, а все пользуются только им и шаблонных классов с виртуальными методами не используют. Ну-ну...
>можно на такие детали не обращать внимания и заняться алгоритмами.
Вот и займитесь этим наконец. Не нужно валить все беды с больной головы на здоровую. Нечего обвинять компилятор во всех грехах. Если что-то тормозит - программист написал плохой код и использовал плохой алгоритм. Из-за вашей экономии на спичках прироста не будет, а как мы выяснили уже - одни беды.
Хаки какие то. Это особенно поможет, когда предков у класса 100500. Заведем бесконечное адресное пространство и будем перед каждым вызовом виртуальных функций делать побитовые операции (мега оптимизация) и не понятно как потом определять, какой виртуальный метод вызвать. При этом со стеком в С++ этот метод ждет фейл и тормоза, тк нужно дергать медленный менеджер памяти, а не быстрый стек. И да, придется сделать "особый магический" менеджер памяти для размещения в разных адресах адресного пространства, предварительно переписав ОС, чтобы та позволяла 100% разместить все объекты по конкретным адресам и быстро. А когда адресное пространство с данным битом заполнится до отказу объектами данного типа, программу ждет bad_alloc при почти пустой памяти, а программиста батхерт. Нечего было создавать так много объектов данного типа! Особенно забатхертятся программисты под DOS. Эпичный пафос и пафосный эпос. (:
fix
DOS? 64-битный DOS?
А по вашему GCC не должен компилировать под DOS или под микроконтроллеры? А то что этот метод не совместим с текущими отлаженными бекендами вас не смущает? А то что этот метод не совместим с текущим стандартом С++, например с кастомными аллокаторами или перегрузкой операторов С++ вас тоже не смущает? А то что это метод сильно замедляет работу со стековыми объектами, взамен мелкой экономии на спичках - вас это также не смущает? А то, что винда не позволяет выделять с гарантией по конкретным адресам объекты и может выделить при этом без смущения по другим? С системой куч винды это не совместимо. И тем более винда не позволяет выделять память по заданным регионам? Вас не смущает, что оси придется переписывать. А не смущает ли вас, что в разных версиях осей какие то диапазоны заняты под системные объекты полностью и не доступны для выделения, зато они доступны в других? Придется под каждую ось компилировать отдельно?
Хватит предлагать котов в вакууме.
К перегрузке операторов это не имеет никакого отношения. Работу с автоматическими переменными практически не замедлит (в той же области видимости всё равно виртуальность исчезнет). Как там в виде не знаю, это её проблема, ну значит будут под неё программы медленнее. А так ведь можно отмапить максимально возможное адресное пространство и использовать по своему усмотрению. Не думал, что в современных ОС прикладные программы работают с физической памятью и по абсолютным адресам. Ну да это проблемы этих ОС (кстати, обычно программы таки компилируются отдельно под разные ОС и под разное железо, иногда даже разными компиляторами).
Мне интересно, где вы его взяли? А С++ компилятор должен компилировать и под DOS и под МК, но я вам уже это говорил. Но видимо, для вас нужно много раз повторить, прежде чем дойдет.
>И почему вы против архитектурно-специфичных оптимизаций?
Вот когда поработаете над кроссплатформенными проектами, тогда и поговорим. См ниже.
>К перегрузке операторов это не имеет никакого отношения.
Да ладно? А как же вы через стандартный malloc затребуете память по определенному региону? Если это архитектуро-специфичная "оптимизация", то как же вы будете перегружать оператор new? Под одну платформу с использованием регионов, а под другую без? Решили добавить ещё одну причину писать программисту дубликаты кода под разные платформы? Обойдемся.
>Работу с автоматическими переменными практически не замедлит.
Замедлит и ещё как. В стеке память выделяется за одну инструкцию проца, а вы предлагаете вызывать тормозную кучу с очень большим не детерминированным временем выделения и освобождения памяти. А между тем автоматических объектов в С++ большинство в среднестатистической программе. "Хорошая" оптимизация у вас получилась...
То, что винда не поддерживают распределение по регионам - это точно. Аналогично и в других осях. Это гениальная идея - переписать винду под С++ с вашей мегооптимизацией. Атцы одобрят.
>Ну да это проблемы этих ОС
По моему эта ваша личная проблема и пользователей вашего "компилятора".
>Не думал
Знаю.
Это (а также другие ваши реплики) показывает, что вы совершенно не слушаете собеседника, а значит продолжать эту дискуссию бессмысленно. А с учётом вашего тона — и нежелательно.
Это вы зачем то приплели 64-битные машины с виртуальной страничной памятью, когда мы говорим про мультиплатформенный С++, работающий почти везде.
>вы совершенно не слушаете собеседника, а значит продолжать эту дискуссию бессмысленно
Аналогично, я про вас это уже сказал и повторятся не буду.
Надо быть сдержаннее. (:
Я один заметил, что зарезервированное слово используется в качестве идентификатора?
А я думал, он должен скомпилировать и убить брата
дельфи штоле?