R>Ну вот и что это такое? Никак дупля не поймаю. Откуда у IInterface1 и IInterface2 по 12 байтов внутрях? По три таблицы что ли? Тогда три таблицы на что? А у ObjStruct откуда тогда 20 байт? Получается пять таблиц что ли (4*5=20)? Опять же: на что указывают эти пять указателей? Ну ладно, на таблицу IBase, на таблицу IInterface1, на таблицу IInterface2, а ещё два куда смотрят?
Это указатели на таблицу оффсетов для перехода от конечного/промежуточного класса к виртуальным базам. Очевидно, для каждого конечного/промежуточного класса смещение базы на этапе компиляции может быть неизвестно, по аналогии с виртуальным вызовом. Пример приведен на вики http://en.wikipedia.org/wiki/Virtual_inheritance.
AS>>1. Это медленнее (скорость вызова). E>Кстати, а почему это (скорость вызова) медленнее? E>Казалось бы, тот же vptr, на ту же thank-функцию...
В случае невиртуального наследования смещения базы/промежуточных классов известны на этапе компиляции. Смещение this корректируется бесплатно.
А чтобы скорректировать значение this в случае виртуального наследования, очевидно, надо и сделать каст через еще одну виртуальную таблицу. Посмотрите статью про размер указателя на функцию, там есть алгоритм вызова.
Здравствуйте, Andrew S, Вы писали:
AS>А чтобы скорректировать значение this в случае виртуального наследования, очевидно, надо и сделать каст через еще одну виртуальную таблицу. Посмотрите статью про размер указателя на функцию, там есть алгоритм вызова.
Тут вроде бы на "Ты" принято?
А что касается корректирования this, то оно "забесплатно" и тогда и тогда.
Единственная проблема -- это когда мы зовём метод интерфейса, по указателю на метод. Но это, IMHO, редкий довольно случай... Особенно редкий, если при этом ещё и существенны накладные расходы на лишнее разыменование указателя на виртуальную таблицу...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
AS>>Я обсуждаю проблемы виртуального наследования. А не частные случаи. E>А я твой тезис о безальтернативности решения из ATL...
E>>>Ну так поделишься данными по реальной стоимости виртуальной базы? Насколько дольше-то? По сравнению с виртуальным вызовом и прологом функции, например?
AS>>Егор, право, неужели поиск отменили? AS>>http://www.rsdn.ru/article/devtools/CppPerformance.xml
E>Что-то как-то я там не нашёл E>1) данных по MSVC
И что? Напомню, кто-то утверждал, что виртуальное наследование не несет накладных расходов на вызов метода.
E>2) вообще случаев, когда вызов метода виртуальной базы значительно бы проигрывал бы вызову метода обычной базы...
E>Тем более, что если бы мы хотели бы сравнить решение с невиртуальным методом виртуальной базы и виртуальным методом невиртуальной базы, то именно такого сравнения вообще вроде бы нет. Но в любом случае мы имеем отличие в 20-25% от времени просто пустого виртуального вызова. Обычно сами по себе вызовы в нормальной программе занимают очень незначительную часть времени работы (нормальные программы либо что-то полезное делают, либо чего-то ждут, а не "хлопают дверками"), так что 20% экономии от незначительной доли времени работы программы -- это, как-то не впечатляет...
Мы хотим сравнить виртуальный метод, разговор шел про абстрактные интерфейсы. Иное сравнивать по мне нет смысла (кстати, есть сомнения, что будет разница для обычных методов?). И не 20% экономии. Отношение от 30 до 80 процентов. Т.е. в плохом случае в ТРИ раза медленнее. На самом деле — примерно в 2 раза, поскольку есть фактически еще один vtbl. Спрашивается, нафига это для абстрактных интерфейсов, а тем более для их реализации?
Итого. Егор, надо уметь проигрывать. А лучше просто не начинать, не будучи уверенным в своих словах. Предлагаю на этом закруглиться, ничего нового я не узнал, и в таком ключе продолжать беседу мне скучно — для меня данная ветка закрыта. Удачи.
... ну вроде как всё понятно ... IInterface1 — таблица функций, IInterface2 — таблица функций, объект наследует два интерфейса, получает две таблицы, соответственно два указателя. В каждой таблице, походу, будет три записи. И насколько я понимаю и в одной и в другой таблицах указатели на методы ObjStruct::AddRef() и ObjStruct::Release() будут указывать на одни и те же места в памяти.
Вот ...
Ежели сделать как следует: то есть повторяющийся базовый класс сделать виртуальным, ну типа чтобы он не повторялся в последнем наследнике дважды, то:
( ... для наглядности начинка интерфейсов опущена ... )
struct IBase { };
struct IInterface1 : virtual public IBase { };
struct IInterface2 : virtual public IBase { };
struct ObjStruct
: public IInterface1
, public IInterface2
{ };
Во время выполнения функции main, вывод следующий:
Ну вот и что это такое? Никак дупля не поймаю. Откуда у IInterface1 и IInterface2 по 12 байтов внутрях? По три таблицы что ли? Тогда три таблицы на что? А у ObjStruct откуда тогда 20 байт? Получается пять таблиц что ли (4*5=20)? Опять же: на что указывают эти пять указателей? Ну ладно, на таблицу IBase, на таблицу IInterface1, на таблицу IInterface2, а ещё два куда смотрят?
При этом если добавить виртуальность и во время наследования интерфейсов IInterface1 и IInterface2:
struct IBase { };
struct IInterface1 : virtual public IBase { };
struct IInterface2 : virtual public IBase { };
struct ObjStruct
: virtual public IInterface1
, virtual public IInterface2
{ };
Во время выполнения функции main, вывод следующий:
То есть получается само по себе виртуальное наследование добавляет ещё один указатель в объект? И что это за зверюга такая, на которую указывает энтот указатель?
Представим, что интерфейсы IBase, IInterface1 и IInterface2 — есть некие библиотечные сущности, которые поменять нельзя. Имеет ли тогда вообще какой-то смысл следующее виртуальное наследование:
struct IBase { };
struct IInterface1 : public IBase { };
struct IInterface2 : public IBase { };
struct ObjStruct
: virtual public IInterface1
, virtual public IInterface2
{ };
Во время выполнения функции main, вывод следующий:
И опять блин, левые четыре байтика как плата за виртуальное наследование ... Рецепторы на известном месте мне подсказывают, что виртуальное наследование интерфейсов IInterface1 и IInterface2 структурой ObjStruct лишено всякого разумного смысла, потому что от ObjStruct никто не наследуется ... Я прав?
Ну а теперь внимание! Самый главный вопрос!
Нужно ли вообще в вышеописанной иерархии интерфейсов/классов использовать виртуальное наследование? Ведь всё так красиво при обычном наследовании! Не прячет ли в самом первом примере двойное невиртуальное наследование структуры IBase каких-то подводных камней? Может виртуальное наследование имеет смысл только при наследовании реальных классов, содержащих некие данные, а в случаях когда одна и та же абсолютно абстрактная структура наследуется несколько раз виртуальное наследование и не нужно вовсе? Как мне корректно отнаследоваться в вышеописанном случае?
P.S.: ... за всю свою практику с виртуальным наследованием интерфейсов столкнулся впервые (ну или может не замечал ранее ), и книжки Страуструпа как назло под рукой нету
P.P.S.: Заранее спасибо всем отозвавшимся.
"Дайте мне возможность выпускать и контролировать деньги в государстве и – мне нет дела до того, кто пишет его законы." (c) Мейер Ансельм Ротшильд , банкир.
Здравствуйте, Rakafon, Вы писали:
R>Ну а теперь внимание! Самый главный вопрос! R>Нужно ли вообще в вышеописанной иерархии интерфейсов/классов использовать виртуальное наследование? Ведь всё так красиво при обычном наследовании! Не прячет ли в самом первом примере двойное невиртуальное наследование структуры IBase каких-то подводных камней? Может виртуальное наследование имеет смысл только при наследовании реальных классов, содержащих некие данные, а в случаях когда одна и та же абсолютно абстрактная структура наследуется несколько раз виртуальное наследование и не нужно вовсе? Как мне корректно отнаследоваться в вышеописанном случае?
R>P.S.: ... за всю свою практику с виртуальным наследованием интерфейсов столкнулся впервые (ну или может не замечал ранее ), и книжки Страуструпа как назло под рукой нету
1) IMHO, в вопросах такого рода некисло бы указывать компилятор. Я так понимаю, что речь идёт о MSVC?
2) При виртуальном наследовании всё становится немного хитро. Дело в том, что объект IBase лежит не внутри IInterface1 и IInterface2, как при обычном наследовании, а отдельно. Соответственно оно нуждается в отдельном vptr.
3) С точки зрения эффективности, IMHO, это всё не очень важно. Намного важнее то, что кститься от IBase к IInterfaceN и обратно надо через виртуальную таблицу/RTTI...
4) При этом надо понимать, что семантика всё-таки разная, Например
5) IMHO, самый важный вопрос тут, это зачем вообще тебе понадобилось наследование интерфейсов? От чего бы не иметь у IBase какой--то варинат QueryInterface, а у IInterfaceN иметь virtual IBase* GetBase()? IMHO, с такими интерфейсами можно работать намного прямее и меньше зависеть от особенностей реализации...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Вопрос о виртуальном наследовании интерфейсов.
Здравствуйте, Andrew S, Вы писали:
AS>По поводу как правильно реализовать то что вы хотите — смотрите ATL, лучше чем там не сделать.
Почему не сделать?
Можно, например, подсчёт ссылок вынести в IBase, от которого наследоваться ВСЕГДА ВИРТУАЛЬНО...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Вопрос о виртуальном наследовании интерфейсов.
AS>>По поводу как правильно реализовать то что вы хотите — смотрите ATL, лучше чем там не сделать.
E>Почему не сделать?
Потому что не сделать. В смысле, можно пробовать сделать по-другому, в результате все-равно будет примерно это же.
E>Можно, например, подсчёт ссылок вынести в IBase, от которого наследоваться ВСЕГДА ВИРТУАЛЬНО...
Нельзя.
1. Это медленнее (скорость вызова).
2. Это весит больше.
3. Лейаут разный для разных компиляторов — межмодульная совместимость невозможна.
4. На этапе создания интерфейса я не знаю будет он использоваться в ромбовидных иерархиях или нет. Если нет — почему я должен платить за то, что мне не надо?
5. Проблему множественное наследование базы составляет только при реализации, для самих интерфейсов наличие нескольких пустых баз (поскольку имеются в виду абстрактные интерфейсы) никакой проблемы не представляет.
Итого.
Проблема не имеет места быть, если наследовать не реализации, а только интерфейсы. Что, собственно, и демонстрирует ATL.
Здравствуйте, Andrew S, Вы писали:
AS>Проблема не имеет места быть, если наследовать не реализации, а только интерфейсы. Что, собственно, и демонстрирует ATL.
Зато реализовывать самый выведенный класс неудобно. Особенно неудобно, если у тебя в реализации есть какие-то ортогональные требования на самый выведенный класс. Например их может наложить какая-нибудь фабрика...
С другой стороны, если независимость от компилятора не нужна, то зачем химичить не ясно.
С другой стороны, если речь идёт о подсчёте ссылок, то накладные расходы на виртуальную базу (лишний указатель + чуть подороже касты) не такие уж и заметные
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[4]: Вопрос о виртуальном наследовании интерфейсов.
Здравствуйте, Andrew S, Вы писали:
AS>1. Это медленнее (скорость вызова).
Кстати, а почему это (скорость вызова) медленнее?
Казалось бы, тот же vptr, на ту же thank-функцию...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
AS>>Проблема не имеет места быть, если наследовать не реализации, а только интерфейсы. Что, собственно, и демонстрирует ATL.
E>Зато реализовывать самый выведенный класс неудобно. Особенно неудобно, если у тебя в реализации есть какие-то ортогональные требования на самый выведенный класс. Например их может наложить какая-нибудь фабрика...
Очень удобно. По мне — не менее удобно, чем в случае виртуального наследования. Тем более, что обычно реализация абстрактного интерфейса сводится к банальному вызову реализации со статическим интерфейсом.
E>С другой стороны, если независимость от компилятора не нужна, то зачем химичить не ясно.
Нужна производительность. И вменяемость лейаута объекта, не только в памяти, но и при, например, конструировании. В накат виртуальное наследование реализации готовит еще несколько сюрпризов с правилами доступа к конструктору базы + неочевидные для обычных людей правила конструирования, особенно, если конструктор базы имеет параметры со значениями по умолчанию.
E>С другой стороны, если речь идёт о подсчёте ссылок, то накладные расходы на виртуальную базу (лишний указатель + чуть подороже касты) не такие уж и заметные
Не сами касты, а вызовы методов промежуточных и базовых классов. Вот тут начинается настоящая филейная часть...
Здравствуйте, Andrew S, Вы писали:
AS>Очень удобно. По мне — не менее удобно, чем в случае виртуального наследования. Тем более, что обычно реализация абстрактного интерфейса сводится к банальному вызову реализации со статическим интерфейсом.
Это с какого такого перепугу? За интерфейсами стоит какая-то объектная модель, к разным аспектам объектов которой интерфейсы и дают доступ. У сложных программ и объектная модель сложная, так что ограничения на её устройство -- не всегда классно...
AS>Нужна производительность. И вменяемость лейаута объекта, не только в памяти, но и при, например, конструировании. В накат виртуальное наследование реализации готовит еще несколько сюрпризов с правилами доступа к конструктору базы + неочевидные для обычных людей правила конструирования, особенно, если конструктор базы имеет параметры со значениями по умолчанию.
Я конечно извиняюсь, но зачем это всё для конструктора IBase, если он только подсчётом ссылок занят? Дефолтный конструктор и конструктор копии, которые оба ставят счётчик в 0, плюс оператор присваивания, который ничего не делает -- вот и всё, что нужно. И никаких сюрпризов.
Дальше, когда компилятор создаёт конструктор самого выведенного объекта, весь лэйаут уже известен -- все вызовы невиртуальные.
Конечно, если в каком-то из конструкторов объектов, выведенных из IBase понадобится получить IBase, то понадобится виртуальный вызов, но это какая-то странная нужда. Тем более, что в ATL-подходе доступа к счётчикам из конструктора объекта получить вообще нельзя -- они ещё не сконструированы
А вот IBase gjkexbт уже можно, и что будет, если мы у него позовём AddRef --
E>>С другой стороны, если речь идёт о подсчёте ссылок, то накладные расходы на виртуальную базу (лишний указатель + чуть подороже касты) не такие уж и заметные
AS>Не сами касты, а вызовы методов промежуточных и базовых классов. Вот тут начинается настоящая филейная часть...
Какая именно? И чем она отличается от случая ATL-реализации и множественного наследования?
Вот есть у меня IInterface1 и IInterface2 и CObj, выведенный из них обоих.
Есть метод IInterface2::f, реализованный в CObj.
Как мы его зовём? Да берём из vptr указатель на тайный метод (условно) IInterface2::call_IInterface2_f_from_CObj, который эквивалентен такому коду:
При этом у нас есть только один виртуальный вызов + статическая коррекция this + сама функция.
При этом, обращаю твоё внимание, что IInterface2::call_IInterface2_f_from_CObj генериться при порождении таблицы виртуальных функций для самого выведенного класса.
А если у нас виртуальная база, то всё происходит РОВНО ТАК ЖЕ...
Потому как и том и там для самого выведенного класса лэйаут известен на момент компиляции...
В любом случае, если ты уверен, что один подход тормознее другого, то у тебя наверное есть тесты производительности?..
Правда, повторюсь
, это всё не очень-то и важно, так как, IMHO, в интерфейсах вообще лучше избегать наследования...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Вопрос о виртуальном наследовании интерфейсов.
Как раз "тут" показано, как в vtable можно запихать всё необходимое.
В результате лэяуты промежуточных классов должны содержать только свой vfptr (откуда из vtable берётся смещение до виртуальной базы) и собственно тело виртуальной базы, содержащее её vfptr — итого 8, а не 12.
А лэяут финального класса — содержит тела промежуточных классов (1 vfptr в каждом), наследуя первый из них для своих нужд, и тело виртуальной базы. Итого 12.
Но MSVC пошло по более расточительному в плане памяти, но более быстрому пути: каждый виртуально унаследованный класс хранит смещение до виртуальной базы прямо в теле, а не в таблице. Отсюда (2+1)*4 = 12 байтов в промежуточных и (2+2+1)*4 = 20 в финальном.
Перекуём баги на фичи!
Re: Вопрос о виртуальном наследовании интерфейсов.
Здарова!
Ого за вечер успели ответить! Круто!
Почитал вас, почитал Страуструпа, ... и всё стало на свои места :)
"Дайте мне возможность выпускать и контролировать деньги в государстве и – мне нет дела до того, кто пишет его законы." (c) Мейер Ансельм Ротшильд , банкир.
Re[3]: Вопрос о виртуальном наследовании интерфейсов.
AS>>Тут более подробно. AS>>http://www.phpcompiler.org/articles/virtualinheritance.html
К>Как раз "тут" показано, как в vtable можно запихать всё необходимое. К>В результате лэяуты промежуточных классов должны содержать только свой vfptr (откуда из vtable берётся смещение до виртуальной базы) и собственно тело виртуальной базы, содержащее её vfptr — итого 8, а не 12.
К>А лэяут финального класса — содержит тела промежуточных классов (1 vfptr в каждом), наследуя первый из них для своих нужд, и тело виртуальной базы. Итого 12.
К>Но MSVC пошло по более расточительному в плане памяти, но более быстрому пути: каждый виртуально унаследованный класс хранит смещение до виртуальной базы прямо в теле, а не в таблице. Отсюда (2+1)*4 = 12 байтов в промежуточных и (2+2+1)*4 = 20 в финальном.
Пример показывает, что это не так (полный пример см. выше по ветке):
AS>>Очень удобно. По мне — не менее удобно, чем в случае виртуального наследования. Тем более, что обычно реализация абстрактного интерфейса сводится к банальному вызову реализации со статическим интерфейсом.
E>Это с какого такого перепугу? За интерфейсами стоит какая-то объектная модель, к разным аспектам объектов которой интерфейсы и дают доступ. У сложных программ и объектная модель сложная, так что ограничения на её устройство -- не всегда классно...
Ограничений как раз тут нет.
AS>>Нужна производительность. И вменяемость лейаута объекта, не только в памяти, но и при, например, конструировании. В накат виртуальное наследование реализации готовит еще несколько сюрпризов с правилами доступа к конструктору базы + неочевидные для обычных людей правила конструирования, особенно, если конструктор базы имеет параметры со значениями по умолчанию.
E>Я конечно извиняюсь, но зачем это всё для конструктора IBase, если он только подсчётом ссылок занят? Дефолтный конструктор и конструктор копии, которые оба ставят счётчик в 0, плюс оператор присваивания, который ничего не делает -- вот и всё, что нужно. И никаких сюрпризов.
Мы говорим про реализацию IBase, которую будут наследовать.
E>Дальше, когда компилятор создаёт конструктор самого выведенного объекта, весь лэйаут уже известен -- все вызовы невиртуальные.
E>Конечно, если в каком-то из конструкторов объектов, выведенных из IBase понадобится получить IBase, то понадобится виртуальный вызов, но это какая-то странная нужда. Тем более, что в ATL-подходе доступа к счётчикам из конструктора объекта получить вообще нельзя -- они ещё не сконструированы E>А вот IBase gjkexbт уже можно, и что будет, если мы у него позовём AddRef --
Ничего не понял, но уверен, что мимо
E>>>С другой стороны, если речь идёт о подсчёте ссылок, то накладные расходы на виртуальную базу (лишний указатель + чуть подороже касты) не такие уж и заметные
AS>>Не сами касты, а вызовы методов промежуточных и базовых классов. Вот тут начинается настоящая филейная часть... E>Какая именно? И чем она отличается от случая ATL-реализации и множественного наследования? E>Вот есть у меня IInterface1 и IInterface2 и CObj, выведенный из них обоих. E>Есть метод IInterface2::f, реализованный в CObj. E>Как мы его зовём? Да берём из vptr указатель на тайный метод (условно) IInterface2::call_IInterface2_f_from_CObj, который эквивалентен такому коду:
При этом у нас есть только один виртуальный вызов + статическая коррекция this + сама функция.
Наверное, у вас свой компилятор. В православных там, где нет виртуального наследования, просто корректируется this и вызывается то, что лежит в vtable. А для msvc и для виртуального наследования никаких переходников нет.
E>При этом, обращаю твоё внимание, что IInterface2::call_IInterface2_f_from_CObj генериться при порождении таблицы виртуальных функций для самого выведенного класса.
Ничего там не генерится. Для CObj генерится свои vtable, куда заносится адрес метода, который реализует вызов. Коррекция this осуществляется статически, поскольку на этапе компиляции известно смещение каждого из подобъектов относительно другого.
E>А если у нас виртуальная база, то всё происходит РОВНО ТАК ЖЕ...
Нет.
E>Потому как и том и там для самого выведенного класса лэйаут известен на момент компиляции...
Для не самого выведенного — не известен. Пример в другой ветке я привел.
E>В любом случае, если ты уверен, что один подход тормознее другого, то у тебя наверное есть тесты производительности?.. E> E>Правда, повторюсь
AS>>А чтобы скорректировать значение this в случае виртуального наследования, очевидно, надо и сделать каст через еще одну виртуальную таблицу. Посмотрите статью про размер указателя на функцию, там есть алгоритм вызова.
E>Тут вроде бы на "Ты" принято? E>А что касается корректирования this, то оно "забесплатно" и тогда и тогда.
E>Единственная проблема -- это когда мы зовём метод интерфейса, по указателю на метод. Но это, IMHO, редкий довольно случай... Особенно редкий, если при этом ещё и существенны накладные расходы на лишнее разыменование указателя на виртуальную таблицу...
Нет, это не так. Указатель на метод это лишь производная проблемы.
Оба раза this собирается в ecx. Разницу видите?
А все почему — в месте вызова AA2::AA1::foo неизвестно смещение AA1 относительно АА2, именно ввиду того, что АА2 является виртуальной базой, а значит смещение определяется иерархией наследования.
Здравствуйте, Andrew S, Вы писали:
AS>Ограничений как раз тут нет.
Когда речь идёт о ATL-подходе, то мы говорим о таком подходе, когда мы пишем самый выведенный класс сами, но потом создаём экземпляр не этого класса, а некоего шаблонного, параметризированного нашим, "самым выведенным"? Так?
Если так, то у фабрик, например, бывают аналогичные желания по сходим причинам...
E>>Я конечно извиняюсь, но зачем это всё для конструктора IBase, если он только подсчётом ссылок занят? Дефолтный конструктор и конструктор копии, которые оба ставят счётчик в 0, плюс оператор присваивания, который ничего не делает -- вот и всё, что нужно. И никаких сюрпризов.
AS>Мы говорим про реализацию IBase, которую будут наследовать.
И что? Какая разница наследовать её или реализовывать каждый раз в шаблонном классе? Во втором случае будет много почти дублирующегося кода...
AS>Ничего не понял, но уверен, что мимо
Если ты ещё раз нахамишь, я не буду с тобой больше разговаривать. То, что ты не понял и уверен большого ума не демонстрирует.
Короче, объясни, где именно неэффективность в конструкторе объекта с виртуальной базой?
AS>Ничего там не генерится. Для CObj генерится свои vtable, куда заносится адрес метода, который реализует вызов. Коррекция this осуществляется статически, поскольку на этапе компиляции известно смещение каждого из подобъектов относительно другого.
Как так, статически? Вот есть у меня IInterface2* p. И зову я у ного p->f(). Как происходит коррекция this статически? Откуда вызывающая сторона знает, что IInterface2 не первая база объекта, в котором реализована f?
AS>Нет.
Аргументированно...
AS>Для не самого выведенного — не известен. Пример в другой ветке я привел.
Ну и что, что для самого невыведенного не известен? Какая разница, вообще? Как часть надо из реализаций методов интерфейса лазить к счётчику ссылок объекта?..
E>>В любом случае, если ты уверен, что один подход тормознее другого, то у тебя наверное есть тесты производительности?..
AS>Почитайте лучше следующее: AS>http://www.lrdev.com/lr/c/virtual.html AS>http://www.devdoc.ru/index.php/content/view/virtual_inheritance.htm
Я про хамство совершенно серьёзно тебе говорю. Это тебя не красит и дискуссию в профильном форуме тоже...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[8]: Вопрос о виртуальном наследовании интерфейсов.
AS>Оба раза this собирается в ecx. Разницу видите? AS>А все почему — в месте вызова AA2::AA1::foo неизвестно смещение AA1 относительно АА2, именно ввиду того, что АА2 является виртуальной базой, а значит смещение определяется иерархией наследования.
1. Посмотри куда передаётся управление, при виртуальном вызове вот в таком вот случае:
struct C1 {
int F1;
virtual int foo() = 0;
};
struct C2 { int F2; };
struct C3 : C2, C1 {
int F3;
virtual int foo() { return F1 + F2 + F3; }
};
int testFoo( C1* p ) { return p->foo(); }
int test()
{
C3 c;
return testFoo( &c );
}
Я уж и не говорю про то, что если делать подсчёт ссылок в IBase, то методы его осуществляющие могут вообще быть невиртуальными. Главное, чтобы деструктор был виртуальным
Но это всё не важно. Важно вообще не это, а совсем другое -- у тебя наверное есть тесты производительности, котрые показывают, что это всё НАМНОГО медленнее?
Может быть ты ими поделишься?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
E>>>Я конечно извиняюсь, но зачем это всё для конструктора IBase, если он только подсчётом ссылок занят? Дефолтный конструктор и конструктор копии, которые оба ставят счётчик в 0, плюс оператор присваивания, который ничего не делает -- вот и всё, что нужно. И никаких сюрпризов.
AS>>Мы говорим про реализацию IBase, которую будут наследовать. E>И что? Какая разница наследовать её или реализовывать каждый раз в шаблонном классе? Во втором случае будет много почти дублирующегося кода...
Разница в том, что параметры по умолчанию в этом случае имеют значение. Для реализации.
AS>>Ничего не понял, но уверен, что мимо E>Если ты ещё раз нахамишь, я не буду с тобой больше разговаривать. То, что ты не понял и уверен большого ума не демонстрирует.
А вы сами то прочитайте, что написали.
E>Короче, объясни, где именно неэффективность в конструкторе объекта с виртуальной базой?
AS>>Ничего там не генерится. Для CObj генерится свои vtable, куда заносится адрес метода, который реализует вызов. Коррекция this осуществляется статически, поскольку на этапе компиляции известно смещение каждого из подобъектов относительно другого.
E>Как так, статически? Вот есть у меня IInterface2* p. И зову я у ного p->f(). Как происходит коррекция this статически? Откуда вызывающая сторона знает, что IInterface2 не первая база объекта, в котором реализована f?
Пример иерархии приведите.
AS>>Нет. E>Аргументированно...
Еще как. Причем, в отличие от вас, я привел конкретный пример с кодом. Где явно видна разница между виртуальным наследованием и отсутствием.
AS>>Для не самого выведенного — не известен. Пример в другой ветке я привел. E>Ну и что, что для самого невыведенного не известен? Какая разница, вообще? Как часть надо из реализаций методов интерфейса лазить к счётчику ссылок объекта?..
Смотрите код. Читайте статьи. Лучше чем там, я точно не объясню.
E>>>В любом случае, если ты уверен, что один подход тормознее другого, то у тебя наверное есть тесты производительности?..
Здравствуйте, Andrew S, Вы писали:
AS>Разница в том, что параметры по умолчанию в этом случае имеют значение. Для реализации.
Зачем считалке ссылок параметры конструктора?
E>>Как так, статически? Вот есть у меня IInterface2* p. И зову я у ного p->f(). Как происходит коррекция this статически? Откуда вызывающая сторона знает, что IInterface2 не первая база объекта, в котором реализована f?
AS>Пример иерархии приведите.
Иерархия всё та же. IBase, из него выведены IInterface1 и IInterface2, а из IInterface1 и IInterface2 выведен CObj.
IInterfaceN могут быть выведены как виртуально, так и нет. Я так понимаю, обсуждается вопрос о накладных расходах, связанных с виртуальным наследованием?
AS>Еще как. Причем, в отличие от вас, я привел конкретный пример с кодом. Где явно видна разница между виртуальным наследованием и отсутствием.
Там, в случае отсутствия, за одно ещё и множественного нет...
AS>Смотрите код. Читайте статьи. Лучше чем там, я точно не объясню.
Ты понимаешь, что нужда вычислять указатель на IBase возникнет только при вызове его методов? При вызове методов IInterface1 или IInterface2 всё будет как обычно?
E>>>>В любом случае, если ты уверен, что один подход тормознее другого, то у тебя наверное есть тесты производительности?..
AS>Есть.
И?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[9]: Вопрос о виртуальном наследовании интерфейсов.
AS>>Оба раза this собирается в ecx. Разницу видите? AS>>А все почему — в месте вызова AA2::AA1::foo неизвестно смещение AA1 относительно АА2, именно ввиду того, что АА2 является виртуальной базой, а значит смещение определяется иерархией наследования.
E>1. Посмотри куда передаётся управление, при виртуальном вызове вот в таком вот случае:
struct C1 {
E> int F1;
E> virtual int foo() = 0;
E>};
E>struct C2 { int F2; };
E>struct C3 : C2, C1 {
E> int F3;
E> virtual int foo() { return F1 + F2 + F3; }
E>};
E>int testFoo( C1* p ) { return p->foo(); }
E>int test()
E>{
E> C3 c;
E> return testFoo( &c );
E>}
Посмотрел. К сожалению, в этом примере студия перемещает С1 в начало объекта С3, поэтому пример не работает. Но не беда, изменим тест.
struct C1 {
int F1;
virtual int foo() = 0;
};
struct C2 { int F2;
virtual void foo1()
{
}
};
struct C3 : C2, C1 {
int F3;
virtual int foo()
{
printf("dd");
return F1 + F2 + F3;
}
};
int testFoo( C1* p, C3 *p1)
{
void *p_0 = &p1->F1;
void *p_1 = &p1->F2;
void *p_2 = &p1->F3;
alloca(100);
printf("cc");
DebugBreak();
p->foo();
return p1->foo();
}
int test()
{
C3 c;
return testFoo( &c, & c);
}
Первая кооректировка this происходит при касте C3 к C1. Что и понятно. Далее в testFoo (я позволил себе вставить маркерные функции в код, чтобы было видно, где мы находимся и не позволить оптимизировать компилятору "слишком просто") происходит корректировка C3 к C1 во втором вызове.
Теперь посмотрим, что из себя представляет виртуальные таблицы С3. Из кода test видно, что их две. foo1 нас не интересует, а foo только в одном экземпляре. Посмотрим, что в ней.
Таким образом foo, изначально появившееся в С1, _всегда_ вызывается с this подобъекта С1, где бы он не находился. А уже конкретная реализация foo корректирует this так, чтобы он соответствовал реальному типу, в данном случае, С3. Она то его всегда знает. А поскольку в месте вызова foo компилятор всегда тем или иным способом может вычислить this для подобъекта, то никаких переходников и не нужно.
Итого. Никаких переходников — тут они не нужны. С другой стороны, я допускаю, что могут быть реализации, имеющие переходники, корректирующие this. Но даже в этом случае для виртуального наследования еще один уровень косвенности для корректировки this необходим ввиду причин, которые я описал ранее.
Т.о., вернувшись к изначальному тезису что виртуальное наследование несет перфоманс пенальти, можно отметить:
1. По вызовам.
2. По кастам.
3. По памяти.
Не так уж и мало, чтобы несколько раз подумать, прежде чем это использовать. Даже без учета "странностей" поведения.
PS Потрясающий способ ведения дискуссий. Пожалуй, на этом надо закругляться.
AS>>Разница в том, что параметры по умолчанию в этом случае имеют значение. Для реализации. E>Зачем считалке ссылок параметры конструктора?
Кто сказал что виртуально наследоваться будут только считалки? Я такого не говорил.
E>>>Как так, статически? Вот есть у меня IInterface2* p. И зову я у ного p->f(). Как происходит коррекция this статически? Откуда вызывающая сторона знает, что IInterface2 не первая база объекта, в котором реализована f?
AS>>Пример иерархии приведите.
E>Иерархия всё та же. IBase, из него выведены IInterface1 и IInterface2, а из IInterface1 и IInterface2 выведен CObj. E>IInterfaceN могут быть выведены как виртуально, так и нет. Я так понимаю, обсуждается вопрос о накладных расходах, связанных с виртуальным наследованием?
Виртуальной является база + (возможно) другие промежуточные классы. Термина "виртуально вывести", к своему стыду, я не знаю.
AS>>Еще как. Причем, в отличие от вас, я привел конкретный пример с кодом. Где явно видна разница между виртуальным наследованием и отсутствием.
E>Там, в случае отсутствия, за одно ещё и множественного нет...
Оно там погоды не делает. Что вы доказали своим следующим примером.
AS>>Смотрите код. Читайте статьи. Лучше чем там, я точно не объясню. E>Ты понимаешь, что нужда вычислять указатель на IBase возникнет только при вызове его методов? При вызове методов IInterface1 или IInterface2 всё будет как обычно?
Ну вот к примеру базовые методы а-ля AddRef/Release должны быть максимально производительными. А для "абстрактных" примитивов синхронизации не то что виртуальное наследование — даже виртуальный вызов бывает виден.
Здравствуйте, Andrew S, Вы писали:
AS>>>Разница в том, что параметры по умолчанию в этом случае имеют значение. Для реализации. E>>Зачем считалке ссылок параметры конструктора?
AS>Кто сказал что виртуально наследоваться будут только считалки? Я такого не говорил.
А разве не такую альтернативу решения из ATL мы обсуждаем примерно отсюда
?..
AS>Ну вот к примеру базовые методы а-ля AddRef/Release должны быть максимально производительными.
Ну так поделишься данными по реальной стоимости виртуальной базы? Насколько дольше-то? По сравнению с виртуальным вызовом и прологом функции, например?
AS>А для "абстрактных" примитивов синхронизации не то что виртуальное наследование — даже виртуальный вызов бывает виден.
А при чём тут тогда интерфейсы и альтернативы ATL?..
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
AS>>>>Разница в том, что параметры по умолчанию в этом случае имеют значение. Для реализации. E>>>Зачем считалке ссылок параметры конструктора?
AS>>Кто сказал что виртуально наследоваться будут только считалки? Я такого не говорил. E>А разве не такую альтернативу решения из ATL мы обсуждаем примерно отсюда
Я обсуждаю проблемы виртуального наследования. А не частные случаи.
AS>>Ну вот к примеру базовые методы а-ля AddRef/Release должны быть максимально производительными. E>Ну так поделишься данными по реальной стоимости виртуальной базы? Насколько дольше-то? По сравнению с виртуальным вызовом и прологом функции, например?
Здравствуйте, Andrew S, Вы писали:
AS>Я обсуждаю проблемы виртуального наследования. А не частные случаи.
А я твой тезис о безальтернативности решения из ATL...
E>>Ну так поделишься данными по реальной стоимости виртуальной базы? Насколько дольше-то? По сравнению с виртуальным вызовом и прологом функции, например?
AS>Егор, право, неужели поиск отменили? AS>http://www.rsdn.ru/article/devtools/CppPerformance.xml
Что-то как-то я там не нашёл
1) данных по MSVC
2) вообще случаев, когда вызов метода виртуальной базы значительно бы проигрывал бы вызову метода обычной базы...
Тем более, что если бы мы хотели бы сравнить решение с невиртуальным методом виртуальной базы и виртуальным методом невиртуальной базы, то именно такого сравнения вообще вроде бы нет. Но в любом случае мы имеем отличие в 20-25% от времени просто пустого виртуального вызова. Обычно сами по себе вызовы в нормальной программе занимают очень незначительную часть времени работы (нормальные программы либо что-то полезное делают, либо чего-то ждут, а не "хлопают дверками"), так что 20% экономии от незначительной доли времени работы программы -- это, как-то не впечатляет...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Andrew S, Вы писали:
AS>И что? Напомню, кто-то утверждал, что виртуальное наследование не несет накладных расходов на вызов метода.
Зависит от реализации...
AS>Мы хотим сравнить виртуальный метод, разговор шел про абстрактные интерфейсы. Иное сравнивать по мне нет смысла (кстати, есть сомнения, что будет разница для обычных методов?). И не 20% экономии. Отношение от 30 до 80 процентов. Т.е. в плохом случае в ТРИ раза медленнее. На самом деле — примерно в 2 раза, поскольку есть фактически еще один vtbl. Спрашивается, нафига это для абстрактных интерфейсов, а тем более для их реализации?
80 процентов не в том сценарии, который предлагалось использовать вообще-то. Кроме того даже эти 80% от очень маленького времени...
AS>Итого. Егор, надо уметь проигрывать. А лучше просто не начинать, не будучи уверенным в своих словах. Предлагаю на этом закруглиться, ничего нового я не узнал, и в таком ключе продолжать беседу мне скучно — для меня данная ветка закрыта. Удачи.
Ты всегда можешь не отвечать, если тебе не интересно
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском