Russian Qt Forum

Программирование => С/C++ => Тема начата: m_ax от Март 19, 2011, 15:50



Название: тип size_t и сравнение указателей
Отправлено: m_ax от Март 19, 2011, 15:50
Приветствую)

У меня такой вопрос:
Имеется два указателя на объекты двух разных типов (A и B).
Хотелось бы сравнивать не сами указатели, а их значения приведённые к типу size_t.
Вот пример:
Код
C++ (Qt)
class A {
...
};
 
class B {
...
};
 
A *a = new A;
B *b = new B;
 
size_t address_a = reinterpret_cast<size_t>(a);
size_t address_b = reinterpret_cast<size_t>(b);
 
if (address_a != address_b)
   ....
else
   ....
 
 
Т.е. корректно ли это будет всегда и везде работать? И есть ли какие-нить грабли?
 ???


Название: Re: тип size_t и сравнение указателей
Отправлено: Igors от Март 19, 2011, 16:05
Т.е. корректно ли это будет всегда и везде работать? И есть ли какие-нить грабли?
 ???
Не хочется делать обязывающих заявлений, но да, всегда. Практичнее/приятнее привести к (char *) вместо size_t


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 19, 2011, 16:08
Т.е. корректно ли это будет всегда и везде работать? И есть ли какие-нить грабли?
 ???
Не хочется делать обязывающих заявлений, но да, всегда. Практичнее/приятнее привести к (char *) вместо size_t
Спасибо)) Прям камень с души)
А в чём приимущество приведения к (char*) по сравнению size_t?


Название: Re: тип size_t и сравнение указателей
Отправлено: Igors от Март 19, 2011, 16:15
А в чём приимущество приведения к (char*) по сравнению size_t?
Чисто субъективное, приведение к  (char *) весьма популярно в чистом "С", т.е. "Вас сразу поймут"  :)


Название: Re: тип size_t и сравнение указателей
Отправлено: SimpleSunny от Март 19, 2011, 16:26
Не должно везде и всюду корректно работать.
Так как никто не гарантирует, что указатель поместится в size_t.


Название: Re: тип size_t и сравнение указателей
Отправлено: Igors от Март 19, 2011, 16:34
Не должно везде и всюду корректно работать.
Так как никто не гарантирует, что указатель поместится в size_t.
Мне кажется это следует из правил адресной арифметики "С" (sizeof возвращает размер типа size_t)


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 19, 2011, 16:40
Я тут нашёл статейку о size_t и ptrdiff_t
http://www.realcoding.net/articles/chto-takoe-sizet-i-ptrdifft.html

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


Название: Re: тип size_t и сравнение указателей
Отправлено: blood_shadow от Март 19, 2011, 18:49
мне кажется будет еще не плохо привести указатели к типу void* и сравнить их на равенство,
к тому же к void* можно неявно привести и операция сравнение это практически единственная что можно делать с void*


Название: Re: тип size_t и сравнение указателей
Отправлено: brankovic от Март 19, 2011, 19:07
Я тут нашёл статейку о size_t и ptrdiff_t
http://www.realcoding.net/articles/chto-takoe-sizet-i-ptrdifft.html

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

У меня были проблемы с реинтерпрет кастами, но я приводил указатель на A к указателю на B и пытался в нём что-то менять. Компилятору это не понравилось, он сгенерил неверный код. Поэтому в gcc такие преобразования делаю через юнион. В случае с size_t должно работать, но более правильно как blood_shadow сказал про void*:

Код
C++ (Qt)
inline bool equal (void *a, void *b) { return a == b; }
 
A *a; B *b;
if (equal (a, b))
 

или

Код
C++ (Qt)
A *a; B *b;
if (std::equal <void *> (a, b))
 

Без реинтерпертов лучше обойтись, чтобы читающих код не пугать.


Название: Re: тип size_t и сравнение указателей
Отправлено: Igors от Март 19, 2011, 19:15
Без реинтерпертов лучше обойтись, чтобы читающих код не пугать.
Верное замечание, незачем выставлять мнимую "крутизну", лучше
Код
C++ (Qt)
if ((char *) A == (char *) B))
 
Скромно и хорошо


Название: Re: тип size_t и сравнение указателей
Отправлено: brankovic от Март 19, 2011, 19:21
Код
C++ (Qt)
if ((char *) A == (char *) B))
 
Скромно и хорошо

сишный каст -- зло!! ..но для краткости сойдёт :)


Название: Re: тип size_t и сравнение указателей
Отправлено: Igors от Март 19, 2011, 19:38
сишный каст -- зло!! ..но для краткости сойдёт :)
Ой, мама дорогая, та где ж там такое зло?  :) Хотели сравнить 2 адреса - ну и сделали. Или адрес уже "не (совсем) адрес" а нечто задроченое с помощью вумных xxx_cast ?  :)


Название: Re: тип size_t и сравнение указателей
Отправлено: brankovic от Март 19, 2011, 19:42
сишный каст -- зло!! ..но для краткости сойдёт :)
Ой, мама дорогая, та где ж там такое зло?  :) Хотели сравнить 2 адреса - ну и сделали. Или адрес уже "не (совсем) адрес" а нечто задроченое с помощью вумных xxx_cast ?  :)

Вот скажет вам компилятор в один прекрасный день: "at line 123, error: сишный каст -- зло!!" и будет прав, а вам и сказать будет нечего.


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 19, 2011, 19:55
Собственно, вопрос возник в контексте оптимизации libssc.
Там ранее при создании нового соединения или, например, при записи приоритета соединения и т.п. каждый раз проверяется весь список соединений и каждый раз при этом вызывается dynamic_cast (который по природе своей не быстр). Вот чтобы избавится от лишних его вызовов, я ввёл в базовый класс (base_connection) виртуальный метод:
Код
C++ (Qt)
typedef size_t address_type // или typedef void* address_type или typedef char* address_type
...
virtual address_type address() const = 0;
 

И соответственно в унаследуемых классов этот метод определяю:
Код
C++ (Qt)
address_type address() const { return reinterpret_cast<address_type>(m_receiver); }
 
Это всё позволяет не вызывать лишний раз dynamic_cast в функции compre:
Код
C++ (Qt)
bool compare(base_connection2<S_arg1, S_arg2> *bc) const {
if ((bc->type() == MEM_FUN_CONNECTION) && (bc->address() == address())) {
   mem_fun_connection2<S_arg1, S_arg2, R_receiver, R_return, R_arg1, R_arg2> *mfc = dynamic_cast<mem_fun_connection2<S_arg1, S_arg2, R_receiver, R_return, R_arg1, R_arg2> *>(bc);
if (m_slot_is_const)
   return (mfc) ? (mfc->m_slot_pack.m_const_slot == m_slot_pack.m_const_slot) : false;
return (mfc) ? (mfc->m_slot_pack.m_slot == m_slot_pack.m_slot) : false;
}
return false;
   }
 
Собственно вот..
Спасибо)


Название: Re: тип size_t и сравнение указателей
Отправлено: brankovic от Март 19, 2011, 20:15
Вот чтобы избавится от лишних его вызовов, я ввёл в базовый класс (base_connection) виртуальный метод:
Код
C++ (Qt)
typedef size_t address_type // или typedef void* address_type или typedef char* address_type
...
virtual address_type address() const = 0;
 

можно уменьшить и убыстрить так:
1. заменить size_t на void *
2. убрать у функции address атрибут virtual и =0
3. в теле address в базовом классе вернуть this (reinterpret_cast не нужен для преобразования в void*)
4. убрать address из наследников

надо иметь ввиду, что вызов dynamic_cast по скорости близок к вызову виртуальной функции, поэтому заменяя dynamic_cast на виртуальную функцию много не выиграешь.

но я бы сделал ещё радикальнее, перед вызовом compare (a, b) вставил бы:

Код
C++ (Qt)
if (static_cast <void*> (&a) == &b)
 

 -- так не будет расходоваться время на вызов виртуальной же функции compare, если адреса a и b сразу не равны.


Название: Re: тип size_t и сравнение указателей
Отправлено: Igors от Март 19, 2011, 20:21
Вот скажет вам компилятор в один прекрасный день: "at line 123, error: сишный каст -- зло!!" и будет прав, а вам и сказать будет нечего.
Мне было "сказать нечего" не один раз, на Mac учат "любить свободу" очень быстро. Жили себе спокойно на Classic. Раз - PowerPC, переписывайте, "просто так" Ваш код не пойдет. Ладно, только улеглось - бац! Carbon - портируйте. Ладно, несколько лет ОС не беспокоил, потом опять: портируйте на Univrsal Binary. Сейчас (тактично) намекают что от Carbon'а надо (срочно) избавляться. А давеча (10.7) Розетту кинули (пользователи на понтах, есть старый но нужный софт)

Так что не верю я что можно написать "ну очень умный код" который уж наверняка будет совместим лет на 10 вперед. Если что случится - значит того не миновать, и нечего пытаться решать проблемы до их поступления


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 19, 2011, 20:26
Цитировать
можно уменьшить и убыстрить так:
1. заменить size_t на void *
2. убрать у функции address атрибут virtual и =0
3. в теле address в базовом классе вернуть this (reinterpret_cast не нужен для преобразования в void*)
4. убрать address из наследников
1. Хорошо
2-4. так не получится по той причине, что мне не интересен адрес самого x_connection: я сравниваю адреса receiver, который является внутренней переменной соединения и содержит адрес объекта receiverА.    

Цитировать
надо иметь ввиду, что вызов dynamic_cast по скорости близок к вызову виртуальной функции, поэтому заменяя dynamic_cast на виртуальную функцию много не выиграешь.
Если так, то тогда получается, что нет смысла в этой оптимизации.  :( ???



Название: Re: тип size_t и сравнение указателей
Отправлено: brankovic от Март 19, 2011, 20:41
1. Хорошо
2-4. так не получится по той причине, что мне не интересен адрес самого x_connection: я сравниваю адреса receiver, который является внутренней переменной соединения и содержит адрес объекта receiverА.    

да, в 2-4 я не прав.

Если так, то тогда получается, что нет смысла в этой оптимизации.  :( ???

Не знаю. Можно иметь в базовом классе void * addr; и его сравнивать. Но тогда в наследниках придётся колдовать с кастами, думаю эта возня не стоит результата. Стоит ли вообще оптимизировать операцию connect? Она ведь редко выполняется, гораздо реже вызова слота.

Вообще в C++0x предлагали ввести мультиметоды для ускорения подобных операций (не знаю, прошёл ли пропозал). Но до светлого будущего редко случается дожить..


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 19, 2011, 20:56
Цитировать
Не знаю. Можно иметь в базовом классе void * addr; и его сравнивать. Но тогда в наследниках придётся колдовать с кастами, думаю эта возня не стоит результата. Стоит ли вообще оптимизировать операцию connect? Она ведь редко выполняется, гораздо реже вызова слота.
Вот я тож об этом сейчас подумал.. Оно того стоит? Если по времени вызов виртуальной функции и вызов оператора dynamic_cast одного порядка то нефиг и суетится..

Цитировать
Вообще в C++0x предлагали ввести мультиметоды для ускорения подобных операций (не знаю, прошёл ли пропозал). Но до светлого будущего редко случается дожить..
Ну почему же) В gcc 4.5.1 уже практически полностью поддерживает стандарт C++0x на сколько мне известно) Гоаорят, что уже лямбды можно использовать)

Кстати, в libsig++ вообще не проверяется при создании нового соединения его наличие. Т.е. если подряд вызвать connect с одним и тем же слотом то слот будет повторно вызван  :(


Название: Re: тип size_t и сравнение указателей
Отправлено: brankovic от Март 19, 2011, 21:05
Так что не верю я что можно написать "ну очень умный код" который уж наверняка будет совместим лет на 10 вперед. Если что случится - значит того не миновать, и нечего пытаться решать проблемы до их поступления

Очень умный не получится, кто же спорит. Но дурные фичи лучше не использовать. Тем более, что касты не так часто нужны, и на 99% это static_cast и dynamic_cast.

Сишный каст зло, потому что:
1. не имеет почти никаких ограничений (reinterpret_cast гораздо слабее), можно кастить тип, а заодно убить константность
2. трудно находится поиском
3. затуманивает смысл (не понятно, какой именно каст хотел сделать автор)

Ну конечно в таком простом примере сишный каст не страшен, но написать static_cast 1 раз не очень сложно, мне кажется. В вашем примере тоже странно выглядит, кстати: (char *) a == (char *) b, читающий на секунду задумается, почему именно char*, может это сравнение строк каких-то? Может в этих классах строки вначале? Может у них operator char * есть? Надо использовать void * для таких вещей. Когда код формально верный, то читается легче, перестаёшь всё время подозревать, что автор -- идиот.


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 19, 2011, 21:10
Цитировать
Так что не верю я что можно написать "ну очень умный код" который уж наверняка будет совместим лет на 10 вперед. Если что случится - значит того не миновать, и нечего пытаться решать проблемы до их поступления

и

Цитировать
Сишный каст зло, потому что:
1. не имеет почти никаких ограничений (reinterpret_cast гораздо слабее), можно кастить тип, а заодно убить константность
2. трудно находится поиском
3. затуманивает смысл (не понятно, какой именно каст хотел сделать автор)

Ну конечно в таком простом примере сишный каст не страшен, но написать static_cast 1 раз не очень сложно, мне кажется. В вашем примере тоже странно выглядит, кстати: (char *) a == (char *) b, читающий на секунду задумается, почему именно char*, может это сравнение строк каких-то? Может в этих классах строки вначале? Может у них operator char * есть? Надо использовать void * для таких вещей. Когда код формально верный, то читается легче, перестаёшь всё время подозревать, что автор -- идиот.

== Мораль: Надо верить))
Хуже не будет)


Название: Re: тип size_t и сравнение указателей
Отправлено: Igors от Март 20, 2011, 10:11
Сишный каст зло, потому что:
1. не имеет почти никаких ограничений (reinterpret_cast гораздо слабее), можно кастить тип, а заодно убить константность
2. трудно находится поиском
3. затуманивает смысл (не понятно, какой именно каст хотел сделать автор)

Ну конечно в таком простом примере сишный каст не страшен, но написать static_cast 1 раз не очень сложно, мне кажется. В вашем примере тоже странно выглядит, кстати: (char *) a == (char *) b, читающий на секунду задумается, почему именно char*, может это сравнение строк каких-то? Может в этих классах строки вначале? Может у них operator char * есть? Надо использовать void * для таких вещей. Когда код формально верный, то читается легче, перестаёшь всё время подозревать, что автор -- идиот.
1) Интересно посмотреть пример показывающий выгоды reinterpret_cast <char *> по сравнению с незатейливым (char *)

2) static_cast не приведет указатель к size_t

3) согласен, для "просто сравнения" (void *) приятнее, но (char *) позволит вычислить разницу между адресами что бывает нужно

4) Спорно/проблематично кто там больше "затуманивает". Часто текст превращается в демонстрацию знания C++ и автор озабочен не содержательной частью а лишь той самой "формальной грамотностью". Продравшись через все навороты обнаруживается, что дела-то "пшик", ф-циональность слаба. А бывает и наоборот

Вот пример совершенно "не кошерного" кода (доставшегося мне в наследство)
Код
C++ (Qt)
void * theNode;
..
if ((size_t) theNode & 1 != 0)  {     // взведен младший бит - это parent нод
Parent * theParent = (Parent *)((size_t) theNode & NODE_MASK);
...
}  
else {
Leaf * theLeaf = (Leaf *) theNode;  // это leaf нод
..
}
 
Автор весьма интенсивно пользует dynamic_cast в др. местах, но здесь решил сделать так - и у него были резоны.


Название: Re: тип size_t и сравнение указателей
Отправлено: brankovic от Март 20, 2011, 11:29
1) Интересно посмотреть пример показывающий выгоды reinterpret_cast <char *> по сравнению с незатейливым (char *)

интересно посмотреть пример показывающий выгоды вытирания рта салфеткой по сравнению с простым рукавом рубашки..

4) Спорно/проблематично кто там больше "затуманивает". Часто текст превращается в демонстрацию знания C++ и автор озабочен не содержательной частью а лишь той самой "формальной грамотностью". Продравшись через все навороты обнаруживается, что дела-то "пшик", ф-циональность слаба. А бывает и наоборот

Я не к тому, что надо писать идеальный код, но туман на пустом месте не нужен. С формальностью тоже легко перебрать -- один раз видел в коде 50 строчек с шаблонами, нужных, чтобы убрать 1 ворнинг о сравнении unsigned >= 0. При прочих равных лучше кривой хак и строчка с комментом, чем формально правильное решение на страницу.

Вот пример совершенно "не кошерного" кода (доставшегося мне в наследство)
...
Автор весьма интенсивно пользует dynamic_cast в др. местах, но здесь решил сделать так - и у него были резоны.

Младший бит указателя всё равно пропадает, стандартная оптимизация, но исполнение спорное (надо задачу видеть, чтобы оценить). Как раз классический пример, когда const легко потерять.


Название: Re: тип size_t и сравнение указателей
Отправлено: Igors от Март 20, 2011, 12:48
1) Интересно посмотреть пример показывающий выгоды reinterpret_cast <char *> по сравнению с незатейливым (char *)
интересно посмотреть пример показывающий выгоды вытирания рта салфеткой по сравнению с простым рукавом рубашки..
Такие аргументы неэффективны  :)  Конечно легко запомнить "юзай  reinterpret_cast, так в C++ положено". А вот привести примерчик где  reinterpret_cast сработает как надо а "просто приведение" нет - ну я например не могу (по крайней мере сходу). Так что вместо элегантной салфетки - большой подгузник  :)


Название: Re: тип size_t и сравнение указателей
Отправлено: brankovic от Март 20, 2011, 13:25
А вот привести примерчик где  reinterpret_cast сработает как надо а "просто приведение" нет

bool chek_set_true (void const *opts, std::string const &opt_name)
{
   cmd_line_options_map & cmd = *(cmd_line_options_map*) opts;
   return cmd [opt_name] == "true";
}

тип параметра opts передать уже нет возможности (ошибка проектирования, но неисправимая, пишется плагин). Автор кастит сишным кастом и теряет const. Вообще-то автор знал, что std::map::operator[] это не константная операция, но проспал этот момент. Если бы был использован reinterpret_cast, то компилятор бы не дал потерять const, ссылка стала бы const, и компилятор ругнулся бы на cmd [opt_name]. А так в мап добавляются новые параметры, на что код в другом месте реагирует презабавным образом.


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 20, 2011, 13:56
У меня тут ещё вопрос возник:
Корректно ли тогда будет такая конструкция работать:
Код
C++ (Qt)
class A
{
public:
   void func1() {}
   void func2() {}
};
 
void *addr1 = (void*)&A::func1;
void *addr2 = (void*)&A::func2;
 
if (addr1 != addr2)
...
elae
...
 
??? Или с указателями на функции такое уже не пройдёт?


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 20, 2011, 14:17
Проверил сейчас, что быстрее работает: вызов виртуальной функции или dynamic_cast:
Разницы практически никакой нет, едва заметный эффект начинает появлятся только после миллиона вызовов.. Так что смысла в этой оптимизации, реально 0.
 


Название: Re: тип size_t и сравнение указателей
Отправлено: brankovic от Март 20, 2011, 14:34
У меня тут ещё вопрос возник:
Корректно ли тогда будет такая конструкция работать:
Код
C++ (Qt)
class A
{
public:
   void func1() {}
   void func2() {}
};
 
void *addr1 = (void*)&A::func1;
void *addr2 = (void*)&A::func2;
 
if (addr1 != addr2)
...
elae
...
 
??? Или с указателями на функции такое уже не пройдёт?

Не пройдёт. И с указателями на члены-данные не пройдёт. Проблема в том, что они могут быть размером в 2 указателя, для классов с виртуальными функциями. К тому же это не настоящие указатели. В общем не стоит их в воид запихивать, даже если сработает, то будет завязано на компилятор.

Проверил сейчас, что быстрее работает: вызов виртуальной функции или dynamic_cast:
Разницы практически никакой нет, едва заметный эффект начинает появлятся только после миллиона вызовов..

и кто побеждает-то?


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 20, 2011, 14:57
Цитировать
и кто побеждает-то?
Получается весьма забавный результат: побеждает dynamic_cast  ???
Вот сам тест:
Код
C++ (Qt)
#include <iostream>
#include <list>
#include <ctime>
#include <cstdlib>
#include <cstdio>
 
using namespace std;
 
enum type_derived { TYPE_INT, TYPE_F };
 
class Base
{
public:
   Base() {}
   virtual ~Base() {}
   virtual void print() = 0;
   virtual type_derived type() const = 0;
};
 
class DerivedInt : public Base
{
public:
   DerivedInt(int id = 0) : m_id(id) {}
   void print() { /*cout << "DerivedInt, id = " << m_id << endl;*/ }
   type_derived type() const { return TYPE_INT; }
   void myFunc() { /*cout << "DerivedInt, id = " << m_id << endl;*/ } // not virtual
private:
   int m_id;
};
 
class DerivedF : public Base
{
public:
   DerivedF(float id = 0.0f) : m_id(id) {}
   void print() { /*cout << "DerivedF, id = " << m_id << endl;*/ }
   type_derived type() const { return TYPE_F; }
   void myFunc() { /*cout << "DerivedF, id = " << m_id << endl;*/ } // not virtual
private:
   float m_id;
};
 
typedef list<Base*> ListBase;
typedef list<Base*>::iterator Iter;
 
void generate(ListBase &l, size_t N) {
   for (size_t i = 0; i < N; ++i) {
       if (rand() % 2)
           l.push_back(new DerivedInt(i));
       else
           l.push_back(new DerivedF(float(i)));
   }
}
 
void clearList(ListBase &l) {
   for (Iter it = l.begin(); it != l.end(); ++it) {
       delete *it;
   }
   l.clear();
}
 
void test_dynamic_cast(ListBase &l) {
   Iter it = l.begin();
   for(; it != l.end(); ++it) {
       DerivedF *obj = dynamic_cast<DerivedF*>(*it);
       if (obj)
           obj->myFunc();
   }
}
 
void test_virtual_fun(ListBase &l) {
   Iter it = l.begin();
   for(; it != l.end(); ++it) {
       if ((*it)->type() == TYPE_F) {
           DerivedF *obj = static_cast<DerivedF*>(*it);
           obj->myFunc();
       }
   }
}
 
int main()
{
   ListBase lb;
   size_t Num = 20000000;
   srand(time(0));
   generate(lb, Num);
 
   clock_t tStart = clock();
   test_dynamic_cast(lb);
   //test_virtual_fun(lb);
   cout << (float)(clock() - tStart) / CLOCKS_PER_SEC << endl;
 
   clearList(lb);
   return 0;
}
 
Суть эксперимента такова:
Есть базовый класс Base с чисто виртуальными двумя функциями print() и type().
От него наследуются два класса DerivedInt и DerivedF в которых данные функции переопределяются.
Но также в этих классах я ввожу не виртуальные методы myFunc().
Далее создаётся список, содержащий указатели на базовый класс Base:
Код
C++ (Qt)
typedef list<Base*> ListBase;
typedef list<Base*>::iterator Iter;
 
   
И заполняется объектами на DerivedInt и DerivedF (случайным образом):
Код
C++ (Qt)
void generate(ListBase &l, size_t N) {
   for (size_t i = 0; i < N; ++i) {
       if (rand() % 2)
           l.push_back(new DerivedInt(i));
       else
           l.push_back(new DerivedF(float(i)));
   }
}
 
Затем проверяется вызовы dynamic_cast виртуальных функций:
Код
C++ (Qt)
void test_dynamic_cast(ListBase &l) {
   Iter it = l.begin();
   for(; it != l.end(); ++it) {
       DerivedF *obj = dynamic_cast<DerivedF*>(*it); // Один вызов dynamic_cast
       if (obj)
           obj->myFunc(); // один вызов не виртуального метода!
   }
}
 
void test_virtual_fun(ListBase &l) {
   Iter it = l.begin();
   for(; it != l.end(); ++it) {
       if ((*it)->type() == TYPE_F) { // Один вызов виртуального метода
           DerivedF *obj = static_cast<DerivedF*>(*it); // Пренебрегаем издержками на вызов static_cast
           obj->myFunc(); // один вызов не виртуального метода!
       }
   }
}
 
Думаю, что тест не совсем чист, поскольку я не учитываю издержки связанные с оператором static_cast


Название: Re: тип size_t и сравнение указателей
Отправлено: brankovic от Март 20, 2011, 16:44
Получается весьма забавный результат: побеждает dynamic_cast  ???

У меня тоже, интересный результат. Наверное из-за того, что функция type что-то делает (возвращает значение), а dynamic cast полностью инлайнится. Ещё, на static_cast нет никаких издержек, он влияет только на этапе проверки программы на ошибки.


Название: Re: тип size_t и сравнение указателей
Отправлено: Igors от Март 20, 2011, 19:45
Код
C++ (Qt)
void test_virtual_fun(ListBase &l) {
   Iter it = l.begin();
   for(; it != l.end(); ++it) {
       if ((*it)->type() == TYPE_F) { // Один вызов виртуального метода
           DerivedF *obj = static_cast<DerivedF*>(*it); // Пренебрегаем издержками на вызов static_cast
           obj->myFunc(); // один вызов не виртуального метода!
       }
   }
}
 
На момент вызова myFunc() obj имеет тип DerivedF - значит никто не мешает компилятору вызвать DerivedF::myFunc() явно, минуя виртуальщину. Просто вызывайте (*it)->myFunc, пусть разбирается

А вообще все эти расходы пренебрежимо малы даже когда надо затачивать скорость. Др. дело типичная ошибка "лучших собаководов STL" - просмотр new/malloc в цикле вычислений (часто грубый, на push_back, insert)


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 20, 2011, 20:42
Код
C++ (Qt)
void test_virtual_fun(ListBase &l) {
   Iter it = l.begin();
   for(; it != l.end(); ++it) {
       if ((*it)->type() == TYPE_F) { // Один вызов виртуального метода
           DerivedF *obj = static_cast<DerivedF*>(*it); // Пренебрегаем издержками на вызов static_cast
           obj->myFunc(); // один вызов не виртуального метода!
       }
   }
}
 
На момент вызова myFunc() obj имеет тип DerivedF - значит никто не мешает компилятору вызвать DerivedF::myFunc() явно, минуя виртуальщину. Просто вызывайте (*it)->myFunc, пусть разбирается
Да нет же, myFunc() - не виртуальная функция) Я бы мог её и не писать вообще, результат бы не изменился. И кстати, я не могу вызвать (*it)->muFunc() поскольку класс Base не имеет этого метода.
Идея теста была сравнить что быстрей: dynamic_cast или вызов виртуальной функции.
Conclusions:
Хотя в данном случае dynamic_cast оказался быстрее, забивать голову и тратить время на подобные оптимизации - бредовая идея)
Лучше потратить его (время) на обдумывание архитектуры и рассмотрения возможных вариантов её расширения в будущем)

Код:
4) Спорно/проблематично кто там больше "затуманивает". Часто текст превращается в демонстрацию знания C++ и автор озабочен не содержательной частью а лишь той самой "формальной грамотностью". Продравшись через все навороты обнаруживается, что дела-то "пшик", ф-циональность слаба. А бывает и наоборот
Формальная грамотность, формальная грамотность.. Грамотность при написании кода - это элементарное уважение к читающему код и залог безопасности (не маловажный факт). Понты, по мне - это когда в коде встречаются сплошь и рядом сомнительные хаки и прочие грабли, на которые разве что не наступит (и то сомнительно) сам автор, в то время, как истинная причина всего этого безобразия - плохая архитектура и чрезмерный С-консерватизм.  :)     
 


Название: Re: тип size_t и сравнение указателей
Отправлено: Akon от Март 20, 2011, 21:25
2 m_ax: попробуй увеличить иерархию наследования и рассматривать динамик-каст самого последнего потомка к самой первой базе.


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 20, 2011, 21:33
2 m_ax: попробуй увеличить иерархию наследования и рассматривать динамик-каст самого последнего потомка к самой первой базе.
Не спорю, вполне вероятно, что при этом ситуация может изменится, но мне как то фиолетово)
У меня реально используется обин базовый класс и два наследуемые от него. Раньше я считал, что в моём случае (пример приведённый выше лишь отражает общую картину) dynamic_cast - очень медленная штука по сравнению с вызовом виртуальной функции, однако, как оказалось для беспокойства нет причин и я оставил вариант с dynamic_cast.   


Название: Re: тип size_t и сравнение указателей
Отправлено: Igors от Март 20, 2011, 22:23
Формальная грамотность, формальная грамотность.. Грамотность при написании кода - это элементарное уважение к читающему код и залог безопасности (не маловажный факт). Понты, по мне - это когда в коде встречаются сплошь и рядом сомнительные хаки и прочие грабли, на которые разве что не наступит (и то сомнительно) сам автор, в то время, как истинная причина всего этого безобразия - плохая архитектура и чрезмерный С-консерватизм.  :)     
Ну давайте немного поспорим. Можно покритиковать Ваш кусочек кода?  :) Вот прямо "близлежащий"
Код
C++ (Qt)
void test_virtual_fun(ListBase &l) {
   Iter it = l.begin();
   for(; it != l.end(); ++it) {
       if ((*it)->type() == TYPE_F) { // Один вызов виртуального метода
           DerivedF *obj = static_cast<DerivedF*>(*it); // Пренебрегаем издержками на вызов static_cast
           obj->myFunc(); // один вызов не виртуального метода!
       }
   }
}
 
Сынтаксыс
- имя "l" просто безобразно, я уже подслеповат и вижу "1"
- TYPE_F выглядит "ну очень в стиле С", (как впрочем и test_virtual_fun). Лучше смотрится Base::TYPE_F
- "type" весьма сомнительное название для метода, а myFunc лишено всякой определенности
- чего это подается ListBase & (а не const ListBase &) если содержимое контейнера указателей остается неизменным? Где же залог безопасности?  :)

Архитектура:
Мне кажется я понял Вашу идею, но тест виртуалов выглядит странно. Зачем грузить тест static_cast и вызовом невиртуала myFunc? Что меряем если львиная доля посвящена другому? Логичнее так (в теле цикла)

Код
C++ (Qt)
Base * base = *it;
int sum = 0;
for (size_t i = 0; i < NUM_CALL; ++i)
sum += base->typeVirtual();
 
А для второго теста так
Код
C++ (Qt)
Base * base = *it;
int sum = 0;
for (size_t i = 0; i < NUM_CALL; ++i) {
DerivedF * derF = dynamic_cast <DerivedF *> (base);
if (derF)
 sum += derF->typeNonVirtial();
else {
 DerivedInt * derInt = dynamic_cast <DerivedInt *> (base);
 if (derInt)
  sum += derInt->typeNonVirtial();
}
}
 
Так что негусто ни с "уважением к читающему" ни с "архитектурой". Однако вынужден признать: с демагогией - все отлично  :)


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 20, 2011, 22:41
Цитировать
Ну давайте немного поспорим. Можно покритиковать Ваш кусочек кода?  Улыбающийся Вот прямо "близлежащий"
Ну я исчо учусь)
Потом этот тест я написал на коленках за минут 10 чисто для себя, чтоб потом его нафиг удалить))
У меня ещё техника не доведена до такого автоматизма, чтоб так прям сразу, даже простой тест, писать идеологически правильно))
Логичнее критиковать то, откуда непосредственно ноги растут: libssc)

Цитировать
Так что негусто ни с "уважением к читающему" ни с "архитектурой". Однако вынужден признать: с демагогией - все отлично  Улыбающийся
Я исправлюсь  :)  


Название: Re: тип size_t и сравнение указателей
Отправлено: Igors от Март 21, 2011, 11:39
А задумка у Вас была хорошая: проверить на деле вместо того чтобы гадать (или утверждать с чужих слов) . Мой тест (исходник приаттачен) печатает такое

Цитировать
TestVirtual time(sec):  0.28 sum = 157286400
TestNonVirtual time(sec):  3.192 sum = 157286400
Примечание "sum" нужна чтобы компилятор не схлопнул код в release

dynamic_cast вызывается дважды, поэтому второе время нужно поделить на 2. Но все равно, dynamic_cast ощутимо (в неск раз) медленнее вызова virtual. Согласен с Akon, что dynamic_cast зависит от сложности построения базового класса.

Немного глянув в отладчике: вроде для "не POD" типа static_cast поет примерно ту же песню что и dynamic_cast (это просто впечатление)

При всем этом: dynamic_cast "достаточно быстр" и экономить на нем неразумно (3 секунды на 200 миллионах вызовов)


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 21, 2011, 12:52
И всё же я считаю, что Ваш тест не совсем чист:
Во-первых, как Вы уже заметили, dynamic_cast в функции TestNonVirtual вызывается дважды.
Во-вторых,
Код
C++ (Qt)
void TestVirtual( const QVector <CBase *> & vec )
{
   QTime begT = QTime::currentTime();
 
       int sum = 0;
       for (int i = 0; i < vec.size(); ++i) {
               CBase * base = vec[i];
               for (size_t j = 0; j < NUM_CALL; ++j)
                       sum += base->typeVirtual(); // Один вызов виртуальной функции
       }
 
       int msecs = begT.msecsTo(QTime::currentTime());
   qDebug() << "TestVirtual time(sec): " << (msecs / 1000.0f) << "sum =" << sum;
}
 
void TestNonVirtual( const QVector <CBase *> & vec )
{
   QTime begT = QTime::currentTime();
 
       int sum = 0;
       for (int i = 0; i < vec.size(); ++i) {
               CBase * base = vec[i];
               for (size_t j = 0; j < NUM_CALL; ++j) {
                       CBaseA * a = dynamic_cast<CBaseA *> (base); // Раз: вызов dynamic_cast
                       if (a)
                               sum += a->typeNonVirtual(); // + Нужно учитывать дополнительное время от вызова обычной функции
                       else {
                               CBaseB * b = dynamic_cast<CBaseB *> (base); // Два: Вызов dynamic_cast
                               if (b)
                                       sum += b->typeNonVirtual(); // Не учитываем добавочное время от этой операции?
                       }
 
               }
       }
 
       int msecs = begT.msecsTo(QTime::currentTime());
   qDebug() << "TestNonVirtual time(sec): " << (msecs / 1000.0f) << "sum =" << sum;
}
 
В функции TestVirtual Виртуальная функция вызывается один (T_v - время вызова виртуальной функции) раз + время на суммирование (T_sum). Всё хорошо.
В функции TestNonVirtual Дважды вызывается dynamic_cast (T_dyn - время вызова) + время вызова не виртуальной функции (T_nv) и + операция суммирования (T_sum).

Я хочу сказать, что нужно сравнивать чистый вызов dynamic_cast и чистый вызов виртуальной функции. Зачем вообще все эти операции суммирования? Оптимизатор можно просто отключить) Например в debug версии.
Кстати, в debug версии мой тест показывает следующие результаты (при трёх запусках):
Код
C++ (Qt)
dynamic_cast:    (1.56, 1.54, 1.54) sec;  <t> = 1.547 sec
virtual_fun:          (1.49, 1.49, 1.48) sec;  <t> = 1.487 sec
 
И получается, что виртуальная функция быстрее dynamic_cast (в данном конкретном случае) в 1.04 раз.


Название: Re: тип size_t и сравнение указателей
Отправлено: m_ax от Март 21, 2011, 13:56
Да, хочу добавить вот что:
Запустил Ваш тест в release и в debug версии со следующим изменением кода:
Код
C++ (Qt)
void TestVirtual( const QVector <CBase *> & vec )
{
   QTime begT = QTime::currentTime();
 
       int sum = 0;
       for (int i = 0; i < vec.size(); ++i) {
               CBase * base = vec[i];
               for (size_t j = 0; j < NUM_CALL; ++j)
                       sum += base->typeVirtual(); // Без изменений
       }
 
       int msecs = begT.msecsTo(QTime::currentTime());
   qDebug() << "TestVirtual time(sec): " << (msecs / 1000.0f) << "sum =" << sum;
}
 
void TestNonVirtual( const QVector <CBase *> & vec )
{
   QTime begT = QTime::currentTime();
 
       int sum = 0;
       for (int i = 0; i < vec.size(); ++i) {
               CBase * base = vec[i];
               for (size_t j = 0; j < NUM_CALL; ++j) {
                        sum += base->typeNonVirtual(); // Для сравнения: оставим только вызов не виртуальной функции
               }
       }
 
       int msecs = begT.msecsTo(QTime::currentTime());
   qDebug() << "TestNonVirtual time(sec): " << (msecs / 1000.0f) << "sum =" << sum;
}
 
Посмотрим, для начала, какую ошибку мы допускаем пренебрегая временем вызова не виртуальной функции. Т.е. сейчас мы сравниваем вирт. и не вирт. функции при прочих равных условиях.
Результат (release):
Код
C++ (Qt)
TestVirtual time(sec):  0.673 sum = 157286400
TestNonVirtual time(sec):  0 sum = 157286400
 
А в debug:
Код
C++ (Qt)
TestVirtual time(sec):  1.529 sum = 157286400
TestNonVirtual time(sec):  0.995 sum = 157286400
 
Сравнивая, можно сделать вывод, что оптимизатор не так просто обмануть, во всяком случае на трюк с суммированием он не ведётся))

Теперь запомним это отношение: время вирт.ф / время не вирт. ф. = 1.529/0.995 = 1.54

И запустим в debug Ваш изначальный вариант:
Код
C++ (Qt)
TestVirtual time(sec):  1.696 sum = 157286400
TestNonVirtual time(sec):  5.348 sum = 157286400
 
Теперь, вспоминая, что время TestNonVirtual нужно поделить на два:
t = 5.348/2 = 2.674
и далее его ещё нужно поделить на отношение, которое было найдено выше, т.е. на 1.54:
В результате имеем:
Код
C++ (Qt)
TestVirtual time(sec):  1.696
TestNonVirtual time(sec):  1.736
 
Отношение этих значений есть: 1.736/1.696 =1.02
Что находится в хорошем согласии с моими результатами в debug версии.