Re: Вопрос о виртуальном наследовании интерфейсов.
От: Andrew S Россия http://alchemy-lab.com
Дата: 05.10.09 18:19
Оценка: 1 (1)
R>
R>struct IBase { };
R>struct IInterface1 : virtual public IBase { };
R>struct IInterface2 : virtual public IBase { };
R>struct ObjStruct
R>    : public IInterface1
R>    , public IInterface2
R>{ };
R>

R>Во время выполнения функции main, вывод следующий:
R>
R>sizeof(IBase) = 4 bytes.
R>sizeof(IInterface1) = 12 bytes.
R>sizeof(IInterface2) = 12 bytes.
R>sizeof(ObjStruct) = 20 bytes.
R>Press any key to continue . . .
R>


R>Ну вот и что это такое? Никак дупля не поймаю. Откуда у IInterface1 и IInterface2 по 12 байтов внутрях? По три таблицы что ли? Тогда три таблицы на что? А у ObjStruct откуда тогда 20 байт? Получается пять таблиц что ли (4*5=20)? Опять же: на что указывают эти пять указателей? Ну ладно, на таблицу IBase, на таблицу IInterface1, на таблицу IInterface2, а ещё два куда смотрят?


Это указатели на таблицу оффсетов для перехода от конечного/промежуточного класса к виртуальным базам. Очевидно, для каждого конечного/промежуточного класса смещение базы на этапе компиляции может быть неизвестно, по аналогии с виртуальным вызовом. Пример приведен на вики
http://en.wikipedia.org/wiki/Virtual_inheritance.

Тут более подробно.
http://www.phpcompiler.org/articles/virtualinheritance.html

По поводу как правильно реализовать то что вы хотите — смотрите ATL, лучше чем там не сделать.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[5]: Вопрос о виртуальном наследовании интерфейсов.
От: Andrew S Россия http://alchemy-lab.com
Дата: 05.10.09 19:17
Оценка: :)
AS>>1. Это медленнее (скорость вызова).
E>Кстати, а почему это (скорость вызова) медленнее?
E>Казалось бы, тот же vptr, на ту же thank-функцию...

В случае невиртуального наследования смещения базы/промежуточных классов известны на этапе компиляции. Смещение this корректируется бесплатно.
А чтобы скорректировать значение this в случае виртуального наследования, очевидно, надо и сделать каст через еще одну виртуальную таблицу. Посмотрите статью про размер указателя на функцию, там есть алгоритм вызова.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[6]: Вопрос о виртуальном наследовании интерфейсов.
От: Erop Россия  
Дата: 05.10.09 19:43
Оценка: -1
Здравствуйте, Andrew S, Вы писали:

AS>А чтобы скорректировать значение this в случае виртуального наследования, очевидно, надо и сделать каст через еще одну виртуальную таблицу. Посмотрите статью про размер указателя на функцию, там есть алгоритм вызова.


Тут вроде бы на "Ты" принято?
А что касается корректирования this, то оно "забесплатно" и тогда и тогда.
Единственная проблема -- это когда мы зовём метод интерфейса, по указателю на метод. Но это, IMHO, редкий довольно случай... Особенно редкий, если при этом ещё и существенны накладные расходы на лишнее разыменование указателя на виртуальную таблицу...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[15]: Первое и последнее предупреждение.
От: Andrew S Россия http://alchemy-lab.com
Дата: 08.10.09 17:22
Оценка: :)
AS>>Я обсуждаю проблемы виртуального наследования. А не частные случаи.
E>А я твой тезис о безальтернативности решения из ATL...

E>>>Ну так поделишься данными по реальной стоимости виртуальной базы? Насколько дольше-то? По сравнению с виртуальным вызовом и прологом функции, например?


AS>>Егор, право, неужели поиск отменили?

AS>>http://www.rsdn.ru/article/devtools/CppPerformance.xml
Автор(ы): Сергей Сацкий, Роман Плеханов
Дата: 31.07.2007
Сравнение производительности кода, сгенерированного различными компиляторами С++ на различных аппаратных платформах. За основу статьи взят материал отчета Technical Report on C++ Performance комитета WG21. Набор тестов расширен, в некоторых случаях предлагаемый код модифицирован. Приведен более подробный анализ возникающих накладных расходов.


E>Что-то как-то я там не нашёл

E>1) данных по MSVC

И что? Напомню, кто-то утверждал, что виртуальное наследование не несет накладных расходов на вызов метода.

E>2) вообще случаев, когда вызов метода виртуальной базы значительно бы проигрывал бы вызову метода обычной базы...


http://rsdn.ru/article/devtools/CppPerformance.xml#EGOBG
Автор(ы): Сергей Сацкий, Роман Плеханов
Дата: 31.07.2007
Сравнение производительности кода, сгенерированного различными компиляторами С++ на различных аппаратных платформах. За основу статьи взят материал отчета Technical Report on C++ Performance комитета WG21. Набор тестов расширен, в некоторых случаях предлагаемый код модифицирован. Приведен более подробный анализ возникающих накладных расходов.


E>Тем более, что если бы мы хотели бы сравнить решение с невиртуальным методом виртуальной базы и виртуальным методом невиртуальной базы, то именно такого сравнения вообще вроде бы нет. Но в любом случае мы имеем отличие в 20-25% от времени просто пустого виртуального вызова. Обычно сами по себе вызовы в нормальной программе занимают очень незначительную часть времени работы (нормальные программы либо что-то полезное делают, либо чего-то ждут, а не "хлопают дверками"), так что 20% экономии от незначительной доли времени работы программы -- это, как-то не впечатляет...


Мы хотим сравнить виртуальный метод, разговор шел про абстрактные интерфейсы. Иное сравнивать по мне нет смысла (кстати, есть сомнения, что будет разница для обычных методов?). И не 20% экономии. Отношение от 30 до 80 процентов. Т.е. в плохом случае в ТРИ раза медленнее. На самом деле — примерно в 2 раза, поскольку есть фактически еще один vtbl. Спрашивается, нафига это для абстрактных интерфейсов, а тем более для их реализации?

Итого. Егор, надо уметь проигрывать. А лучше просто не начинать, не будучи уверенным в своих словах. Предлагаю на этом закруглиться, ничего нового я не узнал, и в таком ключе продолжать беседу мне скучно — для меня данная ветка закрыта. Удачи.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Вопрос о виртуальном наследовании интерфейсов.
От: Rakafon Украина http://rakafon.blogspot.com/
Дата: 05.10.09 16:03
Оценка:
Добрый день коллеги!
Есть вопрос о виртуальном наследовании интерфейсов.

Вот код функции main, дальше опишу классы:
int _tmain(int argc, _TCHAR* argv[])
{
    IInterface1* obj = new ObjStruct;
    obj->AddRef();
    obj->SomeMethod1();

    IInterface2* obj2 = dynamic_cast<IInterface2*>(obj);
    obj2->AddRef();
    obj2->SomeMethod2();

    obj2->Release();
    obj->Release();

    std::cout << "sizeof(IBase) = " << sizeof(IBase) << " bytes. "<< std::endl;
    std::cout << "sizeof(IInterface1) = " << sizeof(IInterface1) << " bytes. "<< std::endl;
    std::cout << "sizeof(IInterface2) = " << sizeof(IInterface2) << " bytes. "<< std::endl;
    std::cout << "sizeof(ObjStruct) = " << sizeof(ObjStruct) << " bytes. "<< std::endl;

    return 0;
}


Допустим есть следующая иерархия классов:

struct IBase
{
    virtual void AddRef() = 0;
    virtual void Release() = 0;
};

struct IInterface1 : public IBase
{
    virtual void SomeMethod1() = 0;
};

struct IInterface2 : public IBase
{
    virtual void SomeMethod2() = 0;
};

struct ObjStruct
    : public IInterface1
    , public IInterface2
{
    void AddRef() { /*std::cout << "AddRef();" << std::endl;*/ };
    void Release() { /*std::cout << "Release();" << std::endl;*/ };
    void SomeMethod1() { /*std::cout << "SomeMethod1();" << std::endl;*/ };
    void SomeMethod2() { /*std::cout << "SomeMethod2();" << std::endl;*/ };
};

Во время выполнения функции main, вывод следующий:
sizeof(IBase) = 4 bytes.
sizeof(IInterface1) = 4 bytes.
sizeof(IInterface2) = 4 bytes.
sizeof(ObjStruct) = 8 bytes.
Press any key to continue . . .

... ну вроде как всё понятно ... 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, вывод следующий:
sizeof(IBase) = 4 bytes.
sizeof(IInterface1) = 12 bytes.
sizeof(IInterface2) = 12 bytes.
sizeof(ObjStruct) = 20 bytes.
Press any key to continue . . .


Ну вот и что это такое? Никак дупля не поймаю. Откуда у 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, вывод следующий:
sizeof(IBase) = 4 bytes.
sizeof(IInterface1) = 12 bytes.
sizeof(IInterface2) = 12 bytes.
sizeof(ObjStruct) = 24 bytes.
Press any key to continue . . .


То есть получается само по себе виртуальное наследование добавляет ещё один указатель в объект? И что это за зверюга такая, на которую указывает энтот указатель?

Представим, что интерфейсы IBase, IInterface1 и IInterface2 — есть некие библиотечные сущности, которые поменять нельзя. Имеет ли тогда вообще какой-то смысл следующее виртуальное наследование:
struct IBase { };
struct IInterface1 : public IBase { };
struct IInterface2 : public IBase { };
struct ObjStruct
    : virtual public IInterface1
    , virtual public IInterface2
{ };

Во время выполнения функции main, вывод следующий:
sizeof(IBase) = 4 bytes.
sizeof(IInterface1) = 4 bytes.
sizeof(IInterface2) = 4 bytes.
sizeof(ObjStruct) = 12 bytes.

И опять блин, левые четыре байтика как плата за виртуальное наследование ... Рецепторы на известном месте мне подсказывают, что виртуальное наследование интерфейсов IInterface1 и IInterface2 структурой ObjStruct лишено всякого разумного смысла, потому что от ObjStruct никто не наследуется ... Я прав?

Ну а теперь внимание! Самый главный вопрос!
Нужно ли вообще в вышеописанной иерархии интерфейсов/классов использовать виртуальное наследование? Ведь всё так красиво при обычном наследовании! Не прячет ли в самом первом примере двойное невиртуальное наследование структуры IBase каких-то подводных камней? Может виртуальное наследование имеет смысл только при наследовании реальных классов, содержащих некие данные, а в случаях когда одна и та же абсолютно абстрактная структура наследуется несколько раз виртуальное наследование и не нужно вовсе? Как мне корректно отнаследоваться в вышеописанном случае?

P.S.: ... за всю свою практику с виртуальным наследованием интерфейсов столкнулся впервые (ну или может не замечал ранее ), и книжки Страуструпа как назло под рукой нету

P.P.S.: Заранее спасибо всем отозвавшимся.
"Дайте мне возможность выпускать и контролировать деньги в государстве и – мне нет дела до того, кто пишет его законы." (c) Мейер Ансельм Ротшильд , банкир.
virtual inheritance
Re: Вопрос о виртуальном наследовании интерфейсов.
От: Erop Россия  
Дата: 05.10.09 18:02
Оценка:
Здравствуйте, Rakafon, Вы писали:

R>Ну а теперь внимание! Самый главный вопрос!

R>Нужно ли вообще в вышеописанной иерархии интерфейсов/классов использовать виртуальное наследование? Ведь всё так красиво при обычном наследовании! Не прячет ли в самом первом примере двойное невиртуальное наследование структуры IBase каких-то подводных камней? Может виртуальное наследование имеет смысл только при наследовании реальных классов, содержащих некие данные, а в случаях когда одна и та же абсолютно абстрактная структура наследуется несколько раз виртуальное наследование и не нужно вовсе? Как мне корректно отнаследоваться в вышеописанном случае?

R>P.S.: ... за всю свою практику с виртуальным наследованием интерфейсов столкнулся впервые (ну или может не замечал ранее ), и книжки Страуструпа как назло под рукой нету


1) IMHO, в вопросах такого рода некисло бы указывать компилятор. Я так понимаю, что речь идёт о MSVC?
2) При виртуальном наследовании всё становится немного хитро. Дело в том, что объект IBase лежит не внутри IInterface1 и IInterface2, как при обычном наследовании, а отдельно. Соответственно оно нуждается в отдельном vptr.
3) С точки зрения эффективности, IMHO, это всё не очень важно. Намного важнее то, что кститься от IBase к IInterfaceN и обратно надо через виртуальную таблицу/RTTI...
4) При этом надо понимать, что семантика всё-таки разная, Например
bool isTheSameObject( IInterface1 * p1, IInterface2 p2) { return p1 == (IBase*)p2; }
будет работать по разному...
Но, в целом я согласен, что в любом раскладе корректнее писать так:
bool isTheSameObject( IInterface1 * p1, IInterface2 p2) { return dynamic_cast<void*>( p1 ) == dynamic_cast<void*>( p2 ); }

5) IMHO, самый важный вопрос тут, это зачем вообще тебе понадобилось наследование интерфейсов? От чего бы не иметь у IBase какой--то варинат QueryInterface, а у IInterfaceN иметь virtual IBase* GetBase()? IMHO, с такими интерфейсами можно работать намного прямее и меньше зависеть от особенностей реализации...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Вопрос о виртуальном наследовании интерфейсов.
От: Erop Россия  
Дата: 05.10.09 18:22
Оценка:
Здравствуйте, Andrew S, Вы писали:

AS>По поводу как правильно реализовать то что вы хотите — смотрите ATL, лучше чем там не сделать.


Почему не сделать?
Можно, например, подсчёт ссылок вынести в IBase, от которого наследоваться ВСЕГДА ВИРТУАЛЬНО...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Вопрос о виртуальном наследовании интерфейсов.
От: Andrew S Россия http://alchemy-lab.com
Дата: 05.10.09 19:00
Оценка:
AS>>По поводу как правильно реализовать то что вы хотите — смотрите ATL, лучше чем там не сделать.

E>Почему не сделать?


Потому что не сделать. В смысле, можно пробовать сделать по-другому, в результате все-равно будет примерно это же.

E>Можно, например, подсчёт ссылок вынести в IBase, от которого наследоваться ВСЕГДА ВИРТУАЛЬНО...


Нельзя.

1. Это медленнее (скорость вызова).
2. Это весит больше.
3. Лейаут разный для разных компиляторов — межмодульная совместимость невозможна.
4. На этапе создания интерфейса я не знаю будет он использоваться в ромбовидных иерархиях или нет. Если нет — почему я должен платить за то, что мне не надо?
5. Проблему множественное наследование базы составляет только при реализации, для самих интерфейсов наличие нескольких пустых баз (поскольку имеются в виду абстрактные интерфейсы) никакой проблемы не представляет.

Итого.

Проблема не имеет места быть, если наследовать не реализации, а только интерфейсы. Что, собственно, и демонстрирует ATL.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[4]: Всё зависит от целей...
От: Erop Россия  
Дата: 05.10.09 19:07
Оценка:
Здравствуйте, Andrew S, Вы писали:

AS>Проблема не имеет места быть, если наследовать не реализации, а только интерфейсы. Что, собственно, и демонстрирует ATL.


Зато реализовывать самый выведенный класс неудобно. Особенно неудобно, если у тебя в реализации есть какие-то ортогональные требования на самый выведенный класс. Например их может наложить какая-нибудь фабрика...

С другой стороны, если независимость от компилятора не нужна, то зачем химичить не ясно.
С другой стороны, если речь идёт о подсчёте ссылок, то накладные расходы на виртуальную базу (лишний указатель + чуть подороже касты) не такие уж и заметные
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[4]: Вопрос о виртуальном наследовании интерфейсов.
От: Erop Россия  
Дата: 05.10.09 19:09
Оценка:
Здравствуйте, Andrew S, Вы писали:

AS>1. Это медленнее (скорость вызова).

Кстати, а почему это (скорость вызова) медленнее?
Казалось бы, тот же vptr, на ту же thank-функцию...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: Всё зависит от целей...
От: Andrew S Россия http://alchemy-lab.com
Дата: 05.10.09 19:14
Оценка:
AS>>Проблема не имеет места быть, если наследовать не реализации, а только интерфейсы. Что, собственно, и демонстрирует ATL.

E>Зато реализовывать самый выведенный класс неудобно. Особенно неудобно, если у тебя в реализации есть какие-то ортогональные требования на самый выведенный класс. Например их может наложить какая-нибудь фабрика...


Очень удобно. По мне — не менее удобно, чем в случае виртуального наследования. Тем более, что обычно реализация абстрактного интерфейса сводится к банальному вызову реализации со статическим интерфейсом.

E>С другой стороны, если независимость от компилятора не нужна, то зачем химичить не ясно.


Нужна производительность. И вменяемость лейаута объекта, не только в памяти, но и при, например, конструировании. В накат виртуальное наследование реализации готовит еще несколько сюрпризов с правилами доступа к конструктору базы + неочевидные для обычных людей правила конструирования, особенно, если конструктор базы имеет параметры со значениями по умолчанию.

E>С другой стороны, если речь идёт о подсчёте ссылок, то накладные расходы на виртуальную базу (лишний указатель + чуть подороже касты) не такие уж и заметные


Не сами касты, а вызовы методов промежуточных и базовых классов. Вот тут начинается настоящая филейная часть...
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[6]: Всё зависит от целей...
От: Erop Россия  
Дата: 05.10.09 19:40
Оценка:
Здравствуйте, 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, который эквивалентен такому коду:
TRes call_IInterface2_f_from_CObj( TArg arg ) { return static_cast<CObj*>(this)->f( arg ); }
При этом у нас есть только один виртуальный вызов + статическая коррекция this + сама функция.
При этом, обращаю твоё внимание, что IInterface2::call_IInterface2_f_from_CObj генериться при порождении таблицы виртуальных функций для самого выведенного класса.
А если у нас виртуальная база, то всё происходит РОВНО ТАК ЖЕ...
Потому как и том и там для самого выведенного класса лэйаут известен на момент компиляции...

В любом случае, если ты уверен, что один подход тормознее другого, то у тебя наверное есть тесты производительности?..


Правда, повторюсь
Автор: Erop
Дата: 05.10.09
, это всё не очень-то и важно, так как, IMHO, в интерфейсах вообще лучше избегать наследования...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Вопрос о виртуальном наследовании интерфейсов.
От: Кодт Россия  
Дата: 05.10.09 20:04
Оценка:
Здравствуйте, Andrew S, Вы писали:

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 в финальном.
Перекуём баги на фичи!
Re: Вопрос о виртуальном наследовании интерфейсов.
От: Rakafon Украина http://rakafon.blogspot.com/
Дата: 06.10.09 08:41
Оценка:
Здарова!
Ого за вечер успели ответить! Круто!
Почитал вас, почитал Страуструпа, ... и всё стало на свои места :)
"Дайте мне возможность выпускать и контролировать деньги в государстве и – мне нет дела до того, кто пишет его законы." (c) Мейер Ансельм Ротшильд , банкир.
Re[3]: Вопрос о виртуальном наследовании интерфейсов.
От: Andrew S Россия http://alchemy-lab.com
Дата: 06.10.09 19:05
Оценка:
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 в финальном.


Пример показывает, что это не так (полный пример см. выше по ветке):


; 1418 :         pa2->foo();

    mov    eax, DWORD PTR _pa2$[esp+4]
    add    esp, 4
    mov    ecx, DWORD PTR [eax+4]  ; ecx = AA2 offset vtable address
    mov    edx, DWORD PTR [ecx+4]  ; edx = AA1 offset from AA2 offset vtable address
    lea    ecx, DWORD PTR [edx+eax+4] ; ecx = AA1_subojbect_this == AA2 address  + offset
    mov    eax, DWORD PTR [edx+eax+4] ; eax = AA1 subobject vtable address.
    call    DWORD PTR [eax]            ; call of the AA1::foo.
    pop    esi


Статья также говорит о другом.

http://www.lrdev.com/lr/c/virtual.html

ну и это.

http://www.devdoc.ru/index.php/content/view/virtual_inheritance.htm
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[7]: Всё зависит от целей...
От: Andrew S Россия http://alchemy-lab.com
Дата: 06.10.09 19:09
Оценка:
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, который эквивалентен такому коду:
TRes call_IInterface2_f_from_CObj( TArg arg ) { return static_cast<CObj*>(this)->f( arg ); }
При этом у нас есть только один виртуальный вызов + статическая коррекция this + сама функция.


Наверное, у вас свой компилятор. В православных там, где нет виртуального наследования, просто корректируется this и вызывается то, что лежит в vtable. А для msvc и для виртуального наследования никаких переходников нет.

E>При этом, обращаю твоё внимание, что IInterface2::call_IInterface2_f_from_CObj генериться при порождении таблицы виртуальных функций для самого выведенного класса.


Ничего там не генерится. Для CObj генерится свои vtable, куда заносится адрес метода, который реализует вызов. Коррекция this осуществляется статически, поскольку на этапе компиляции известно смещение каждого из подобъектов относительно другого.

E>А если у нас виртуальная база, то всё происходит РОВНО ТАК ЖЕ...


Нет.

E>Потому как и том и там для самого выведенного класса лэйаут известен на момент компиляции...


Для не самого выведенного — не известен. Пример в другой ветке я привел.

E>В любом случае, если ты уверен, что один подход тормознее другого, то у тебя наверное есть тесты производительности?..

E>

E>Правда, повторюсь
Автор: Erop
Дата: 05.10.09
, это всё не очень-то и важно, так как, IMHO, в интерфейсах вообще лучше избегать наследования...


Почитайте лучше следующее:
http://www.lrdev.com/lr/c/virtual.html
http://www.devdoc.ru/index.php/content/view/virtual_inheritance.htm
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[7]: Вопрос о виртуальном наследовании интерфейсов.
От: Andrew S Россия http://alchemy-lab.com
Дата: 06.10.09 19:11
Оценка:
AS>>А чтобы скорректировать значение this в случае виртуального наследования, очевидно, надо и сделать каст через еще одну виртуальную таблицу. Посмотрите статью про размер указателя на функцию, там есть алгоритм вызова.

E>Тут вроде бы на "Ты" принято?

E>А что касается корректирования this, то оно "забесплатно" и тогда и тогда.

E>Единственная проблема -- это когда мы зовём метод интерфейса, по указателю на метод. Но это, IMHO, редкий довольно случай... Особенно редкий, если при этом ещё и существенны накладные расходы на лишнее разыменование указателя на виртуальную таблицу...


Нет, это не так. Указатель на метод это лишь производная проблемы.

struct AA1
{
    virtual void foo()
    {
    }
};

struct AA2: virtual public AA1
{
    virtual void foo1()
    {
    }
};

struct AA3: virtual public AA1
{
    virtual void foo2()
    {
    }
};

struct AA4: public AA2, public AA3
{
};


void test(AA1 *pa1, AA2*pa2)
{
    getch();
        printf("sfsdf");

        pa1->foo();

        printf("sfsdf");
        
        pa2->foo();



}


И вот что получается из этого:

;    COMDAT ?test@@YAXPAUAA1@@PAUAA2@@@Z
_TEXT    SEGMENT
_pa1$ = 8
_pa2$ = 12
?test@@YAXPAUAA1@@PAUAA2@@@Z PROC NEAR            ; test, COMDAT

; 1410 : {

    push    esi

; 1411 :     getch();

    call    DWORD PTR __imp__getch

; 1412 :         printf("sfsdf");

    mov    esi, DWORD PTR __imp__printf
    push    OFFSET FLAT:??_C@_05PDMO@sfsdf?$AA@    ; string
    call    esi

; 1413 : 
; 1414 :         pa1->foo();

    mov    ecx, DWORD PTR _pa1$[esp+4]
    add    esp, 4
    mov    eax, DWORD PTR [ecx]
    call    DWORD PTR [eax]

; 1415 : 
; 1416 :         printf("sfsdf");

    push    OFFSET FLAT:??_C@_05PDMO@sfsdf?$AA@    ; string
    call    esi

; 1417 :         
; 1418 :         pa2->foo();

; 1418 :         pa2->foo();

    mov    eax, DWORD PTR _pa2$[esp+4]
    add    esp, 4
    mov    ecx, DWORD PTR [eax+4]  ; ecx = AA2 offset vtable address
    mov    edx, DWORD PTR [ecx+4]  ; edx = AA1 offset from AA2 offset vtable address
    lea    ecx, DWORD PTR [edx+eax+4] ; ecx = AA1_subojbect_this == AA2 address  + offset
    mov    eax, DWORD PTR [edx+eax+4] ; eax = AA1 subobject vtable address.
    call    DWORD PTR [eax]            ; call of the AA1::foo.
    pop    esi

; 1419 : 
; 1420 : 
; 1421 : 
; 1422 : }

    ret    0
?test@@YAXPAUAA1@@PAUAA2@@@Z ENDP            ; test
_TEXT    ENDS


Оба раза this собирается в ecx. Разницу видите?
А все почему — в месте вызова AA2::AA1::foo неизвестно смещение AA1 относительно АА2, именно ввиду того, что АА2 является виртуальной базой, а значит смещение определяется иерархией наследования.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[8]: Первое и последнее предупреждение.
От: Erop Россия  
Дата: 06.10.09 21:20
Оценка:
Здравствуйте, 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]: Вопрос о виртуальном наследовании интерфейсов.
От: Erop Россия  
Дата: 06.10.09 21:42
Оценка:
Здравствуйте, Andrew S, Вы писали:


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, то методы его осуществляющие могут вообще быть невиртуальными. Главное, чтобы деструктор был виртуальным


Но это всё не важно. Важно вообще не это, а совсем другое -- у тебя наверное есть тесты производительности, котрые показывают, что это всё НАМНОГО медленнее?
Может быть ты ими поделишься?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[9]: Первое и последнее предупреждение.
От: Andrew S Россия http://alchemy-lab.com
Дата: 06.10.09 22:02
Оценка:
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>>>В любом случае, если ты уверен, что один подход тормознее другого, то у тебя наверное есть тесты производительности?..


Есть.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[10]: Первое и последнее предупреждение.
От: Erop Россия  
Дата: 06.10.09 22:10
Оценка:
Здравствуйте, 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]: Вопрос о виртуальном наследовании интерфейсов.
От: Andrew S Россия http://alchemy-lab.com
Дата: 06.10.09 23:04
Оценка:
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);
}



Итак смотрим, что же там. А там все тривиально:


;    COMDAT ?test@@YAHXZ
_TEXT    SEGMENT
_c$ = -20
?test@@YAHXZ PROC NEAR                    ; test, COMDAT

; 1472 : {

    sub    esp, 20                    ; 00000014H

; 1473 :     C3 c;
; 1474 :     return testFoo( &c, & c);

    lea    eax, DWORD PTR _c$[esp+20]
    lea    ecx, DWORD PTR _c$[esp+28]
    neg    eax
    sbb    eax, eax
    lea    edx, DWORD PTR _c$[esp+20]
    and    eax, ecx
    push    edx
    push    eax
    mov    DWORD PTR _c$[esp+28], OFFSET FLAT:??_7C3@@6BC2@@@ ; C3::`vftable'
    mov    DWORD PTR _c$[esp+36], OFFSET FLAT:??_7C3@@6BC1@@@ ; C3::`vftable'
    call    ?testFoo@@YAHPAUC1@@PAUC3@@@Z        ; testFoo

; 1475 : }

    add    esp, 28                    ; 0000001cH
    ret    0
?test@@YAHXZ ENDP                    ; test
_TEXT    ENDS


;    COMDAT ?testFoo@@YAHPAUC1@@PAUC3@@@Z
_TEXT    SEGMENT
_p$ = 8
_p1$ = 12
?testFoo@@YAHPAUC1@@PAUC3@@@Z PROC NEAR            ; testFoo, COMDAT

; 1460 : {

    push    ebp
    mov    ebp, esp

; 1461 :     void *p_0 = &p1->F1;
; 1462 :     void *p_1 = &p1->F2;
; 1463 :     void *p_2 = &p1->F3;
; 1464 :     alloca(100);

    mov    eax, 100                ; 00000064H
    call    __alloca_probe

; 1465 :     printf("cc");

    push    OFFSET FLAT:??_C@_02EMJB@cc?$AA@    ; `string'
    call    DWORD PTR __imp__printf
    add    esp, 4

; 1466 :     DebugBreak();

    call    DWORD PTR __imp__DebugBreak@0

; 1467 :     p->foo();

    mov    ecx, DWORD PTR _p$[ebp]
    mov    eax, DWORD PTR [ecx]
    call    DWORD PTR [eax]

; 1468 :     return p1->foo();

    mov    ecx, DWORD PTR _p1$[ebp]
    add    ecx, 8
    mov    edx, DWORD PTR [ecx]
    call    DWORD PTR [edx]

; 1469 : }

    lea    esp, DWORD PTR [ebp]
    pop    ebp
    ret    0
?testFoo@@YAHPAUC1@@PAUC3@@@Z ENDP            ; testFoo
_TEXT    ENDS


Первая кооректировка this происходит при касте C3 к C1. Что и понятно. Далее в testFoo (я позволил себе вставить маркерные функции в код, чтобы было видно, где мы находимся и не позволить оптимизировать компилятору "слишком просто") происходит корректировка C3 к C1 во втором вызове.

Теперь посмотрим, что из себя представляет виртуальные таблицы С3. Из кода test видно, что их две. foo1 нас не интересует, а foo только в одном экземпляре. Посмотрим, что в ней.


CONST    SEGMENT
??_7C3@@6BC2@@@ DD FLAT:?foo1@C2@@UAEXXZ        ; C3::`vftable'
CONST    ENDS
;    COMDAT ??_7C3@@6BC1@@@
CONST    SEGMENT
??_7C3@@6BC1@@@ DD FLAT:?foo@C3@@UAEHXZ            ; C3::`vftable'
CONST    ENDS


_TEXT    SEGMENT
?foo@C3@@UAEHXZ PROC NEAR                ; C3::foo, COMDAT

; 1453 :     {

    push    esi
    mov    esi, ecx

; 1454 :         printf("dd");

    push    OFFSET FLAT:??_C@_02HICB@dd?$AA@    ; `string'
    call    DWORD PTR __imp__printf

; 1455 :         return F1 + F2 + F3;

    mov    eax, DWORD PTR [esi+8]
    mov    edx, DWORD PTR [esi-4]
    mov    ecx, DWORD PTR [esi+4]
    add    esp, 4
    add    eax, edx
    add    eax, ecx
    pop    esi

; 1456 :     }

    ret    0
?foo@C3@@UAEHXZ ENDP                    ; C3::foo
_TEXT    ENDS


Таким образом foo, изначально появившееся в С1, _всегда_ вызывается с this подобъекта С1, где бы он не находился. А уже конкретная реализация foo корректирует this так, чтобы он соответствовал реальному типу, в данном случае, С3. Она то его всегда знает. А поскольку в месте вызова foo компилятор всегда тем или иным способом может вычислить this для подобъекта, то никаких переходников и не нужно.

Итого. Никаких переходников — тут они не нужны. С другой стороны, я допускаю, что могут быть реализации, имеющие переходники, корректирующие this. Но даже в этом случае для виртуального наследования еще один уровень косвенности для корректировки this необходим ввиду причин, которые я описал ранее.

Т.о., вернувшись к изначальному тезису что виртуальное наследование несет перфоманс пенальти, можно отметить:
1. По вызовам.
2. По кастам.
3. По памяти.

Не так уж и мало, чтобы несколько раз подумать, прежде чем это использовать. Даже без учета "странностей" поведения.

PS Потрясающий способ ведения дискуссий. Пожалуй, на этом надо закругляться.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[11]: Первое и последнее предупреждение.
От: Andrew S Россия http://alchemy-lab.com
Дата: 06.10.09 23:11
Оценка:
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 должны быть максимально производительными. А для "абстрактных" примитивов синхронизации не то что виртуальное наследование — даже виртуальный вызов бывает виден.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[12]: Первое и последнее предупреждение.
От: Erop Россия  
Дата: 07.10.09 03:08
Оценка:
Здравствуйте, Andrew S, Вы писали:

AS>>>Разница в том, что параметры по умолчанию в этом случае имеют значение. Для реализации.

E>>Зачем считалке ссылок параметры конструктора?

AS>Кто сказал что виртуально наследоваться будут только считалки? Я такого не говорил.

А разве не такую альтернативу решения из ATL мы обсуждаем примерно отсюда
Автор: Erop
Дата: 05.10.09
?..

AS>Ну вот к примеру базовые методы а-ля AddRef/Release должны быть максимально производительными.

Ну так поделишься данными по реальной стоимости виртуальной базы? Насколько дольше-то? По сравнению с виртуальным вызовом и прологом функции, например?

AS>А для "абстрактных" примитивов синхронизации не то что виртуальное наследование — даже виртуальный вызов бывает виден.

А при чём тут тогда интерфейсы и альтернативы ATL?..
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[13]: Первое и последнее предупреждение.
От: Andrew S Россия http://alchemy-lab.com
Дата: 07.10.09 18:15
Оценка:
AS>>>>Разница в том, что параметры по умолчанию в этом случае имеют значение. Для реализации.
E>>>Зачем считалке ссылок параметры конструктора?

AS>>Кто сказал что виртуально наследоваться будут только считалки? Я такого не говорил.

E>А разве не такую альтернативу решения из ATL мы обсуждаем примерно отсюда
Автор: Erop
Дата: 05.10.09
?..


Я обсуждаю проблемы виртуального наследования. А не частные случаи.

AS>>Ну вот к примеру базовые методы а-ля AddRef/Release должны быть максимально производительными.

E>Ну так поделишься данными по реальной стоимости виртуальной базы? Насколько дольше-то? По сравнению с виртуальным вызовом и прологом функции, например?

Егор, право, неужели поиск отменили?
http://www.rsdn.ru/article/devtools/CppPerformance.xml
Автор(ы): Сергей Сацкий, Роман Плеханов
Дата: 31.07.2007
Сравнение производительности кода, сгенерированного различными компиляторами С++ на различных аппаратных платформах. За основу статьи взят материал отчета Technical Report on C++ Performance комитета WG21. Набор тестов расширен, в некоторых случаях предлагаемый код модифицирован. Приведен более подробный анализ возникающих накладных расходов.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[14]: Первое и последнее предупреждение.
От: Erop Россия  
Дата: 07.10.09 22:59
Оценка:
Здравствуйте, Andrew S, Вы писали:

AS>Я обсуждаю проблемы виртуального наследования. А не частные случаи.

А я твой тезис о безальтернативности решения из ATL...

E>>Ну так поделишься данными по реальной стоимости виртуальной базы? Насколько дольше-то? По сравнению с виртуальным вызовом и прологом функции, например?


AS>Егор, право, неужели поиск отменили?

AS>http://www.rsdn.ru/article/devtools/CppPerformance.xml
Автор(ы): Сергей Сацкий, Роман Плеханов
Дата: 31.07.2007
Сравнение производительности кода, сгенерированного различными компиляторами С++ на различных аппаратных платформах. За основу статьи взят материал отчета Technical Report on C++ Performance комитета WG21. Набор тестов расширен, в некоторых случаях предлагаемый код модифицирован. Приведен более подробный анализ возникающих накладных расходов.


Что-то как-то я там не нашёл
1) данных по MSVC
2) вообще случаев, когда вызов метода виртуальной базы значительно бы проигрывал бы вызову метода обычной базы...
Тем более, что если бы мы хотели бы сравнить решение с невиртуальным методом виртуальной базы и виртуальным методом невиртуальной базы, то именно такого сравнения вообще вроде бы нет. Но в любом случае мы имеем отличие в 20-25% от времени просто пустого виртуального вызова. Обычно сами по себе вызовы в нормальной программе занимают очень незначительную часть времени работы (нормальные программы либо что-то полезное делают, либо чего-то ждут, а не "хлопают дверками"), так что 20% экономии от незначительной доли времени работы программы -- это, как-то не впечатляет...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[16]: Первое и последнее предупреждение.
От: Erop Россия  
Дата: 08.10.09 18:20
Оценка:
Здравствуйте, Andrew S, Вы писали:

AS>И что? Напомню, кто-то утверждал, что виртуальное наследование не несет накладных расходов на вызов метода.

Зависит от реализации...


AS>Мы хотим сравнить виртуальный метод, разговор шел про абстрактные интерфейсы. Иное сравнивать по мне нет смысла (кстати, есть сомнения, что будет разница для обычных методов?). И не 20% экономии. Отношение от 30 до 80 процентов. Т.е. в плохом случае в ТРИ раза медленнее. На самом деле — примерно в 2 раза, поскольку есть фактически еще один vtbl. Спрашивается, нафига это для абстрактных интерфейсов, а тем более для их реализации?


80 процентов не в том сценарии, который предлагалось использовать вообще-то. Кроме того даже эти 80% от очень маленького времени...

AS>Итого. Егор, надо уметь проигрывать. А лучше просто не начинать, не будучи уверенным в своих словах. Предлагаю на этом закруглиться, ничего нового я не узнал, и в таком ключе продолжать беседу мне скучно — для меня данная ветка закрыта. Удачи.


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