AndrewVK,
> ПК> Не совсем: тамошнему поведению объяснение исключительно в пожелании так сделать, в стремлении к безопасности, т.е. предотвратить работу с несконструированным классом. Боксинг же — следствие оптимизации.
> Какой оптимизации?
Оптимизация была сделана ранее: примитивные типы отделили от остальных, хотя на логическом уровне оставили их наследниками Object. Вот это и было оптимизацией. Боксинг — следствие принятой (для оптимизации) модели.
> К примеру держишь ты в памяти строку из БД. А там и int, и float, и string, и byte[]. И хранить это нужно в одной коллекции. Вот тут тебе и придется либо боксить, либо приводить все к void*.
Верно. Но есть и другие случаи полиморфной работы с объектами разных типов, где можно было бы обойтись без боксинга. В предложенной модели это организуется легко путем привязки интерфейсов в момент преобразования, а не в момент проектирования типов.
Posted via RSDN NNTP Server 1.9
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
CS>>IAnimal — это просто список сигнатур функций в compile time. В runtime это указатель на статическую таблицу адресов.
AVK>Но для каждого класса эта таблица будет своей!
CS>>Получение (суть построение) такого списка ( таблицы адресов методов) для данного класса — тривиальная задача компилятора/компоновщика.
AVK>Но ты же не знаешь у какого класса какой интерфейс мы попросим. В результате тебе придется строить такие таблицы для всех валидных комбинаций классов и интерфейсов. Итого имея 100 интерфейсов и 100 классов, которые по сигнатуре к ним подходят, получим что нужно будет построить 10000 таких таблиц.
Ты не понял. Еще раз — это фактически механизм template instantiation чего в С# в принципе нет.
template<typename T>
void foo(T t)
{
t.bar();
}
Эта декларация не значит того что я генерю вызовы bar() для всех возможных классов, правда?
Т.е. интерфейсы строятся on demand — только тогда когда я этого прошу и хочу.
CS>>Давай я еще раз подчеркну основные фенечки: CS>>
CS>>Polymorphism without Space Overhead
CS>>Because polymorphism is provided externally to a class, it means that any class can be used polymorphically and also that there is no overhead of vtable pointers within a class. Most OOP languages, if not all, require a significant overhead to provide polymorphic behaviour.
CS>>The only caveat is that the interface references are typically twice the width of an an ordinary reference ( technically this is not neccessarily the case, as I will explain below ). On the other hand the overhead of multiple interface implementation in other languages is considerably more significant.
CS>>Polymorphism only when Needed
CS>>Another advantage of the Heron interfaces approach, is that you don't need to incur any of the cost of run-time polymorphism, until when and if you explicitly require it. That cost includes the inability to inline functions, the performance hit of dynamic dispatch and the space overhead of function dispatch tables.
AVK>Ну собственно в дотнете все эти бенефиты присутствуют, притом побочных эффектов куда меньше.
Извини, но последний раз эту фразу я слышал в варианте "все равно его не брошу потому что он хороший" примерно с тем же уровнем аргументации.
Ну как так можно?
Ты же сам говоришь про побочные эффекты....
Этот неуклюжий боксинг, imap и все остальное просто не нужны при наличии template/heron::interface. Или это не понятно?
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Делается все это во время компиляции. Работает ли это "в полиморфном коде" — зависит от определения "полиморфного кода". Если речь идет о коде, работающем через ссылки на интерфейсы, то работать будет. Если о чем-то другом — тогда я тебя просто не понимаю.
Здравствуйте, c-smile, Вы писали:
AVK>>Но ты же не знаешь у какого класса какой интерфейс мы попросим. В результате тебе придется строить такие таблицы для всех валидных комбинаций классов и интерфейсов. Итого имея 100 интерфейсов и 100 классов, которые по сигнатуре к ним подходят, получим что нужно будет построить 10000 таких таблиц.
CS>Ты не понял. Еще раз — это фактически механизм template instantiation чего в С# в принципе нет.
Это ты похоже чего то непонимаешь. Еще раз пример, более подробно:
class Base {}
interface IA
{
void Foo();
}
class X : Base, IA
{
void Bar1(); // адрес метода в памяти 0001void Foo(); // адрес метода в памяти 0002
}
class Y : Base, IA
{
void Foo(); // адрес метода в памяти 0003void Bar2(); // адрес метода в памяти 0004
}
...
Base[] bs = new Base[]{new X(). new Y()};
(IA)bs[0].Foo(); // здесь должен быть вызов по адресу 0002
(IA)bs[1].Foo(); // а здесь должен быть вызов по адресу 0003
Теперь поговорим о реализации
1) Неполиморфным вызов в данном случае быть не может. Не имеет смысла это даже обсуждать.
2) Поскольку в базовом интерфейсе реализация метода Foo() не присутствует, а компилятору ничего больше не известно, то он не то что что статический адрес подставить не может, но и не может произвести даже виртуальный вызов, поскольку не может определить смещение в VMT.
3) Отсюда для интерфейсов нужна двойная виртуальность. Разруливается это на практике обычно так:
а) Ресолвим имена методов в рантайме. Так собственно работает IDispatch. Очень гибко, но весьма медленно.
б) Для класса строится таблица соответствия индексов в vmt класса и vmt интерфейса. Собственно именно этот способ используется в дотнете. Самый быстрый способ, не требует приведения типов, но довольно проблематична реализация в компайл-тайме.
в) Для класса создается несколько VMT, по одной на каждый интерфейс. Этот способ применяется в C++ и Дельфи. Довольно быстрый, но требует определенной химии в рантайме для определения нужной VMT.
Теория закончена, перейдем к практике. С вариантом а все понятно — никакой особой помощи со стороный компилятора она не требует. Строим хеш с именами и адресами, потом при вызове для каждого экземпляра ресолвим строковое наименование метода. С вариантом б я ситуацию расписывал — при загрузке класса (можно в принципе и при компиляции, но при этом, как ты справедливо заметил, нужно будет разруливать конфликты в многомодульной конфигурации) строится таблица соотвествия. После этого все сводится к обычному табличному преобразованию смещений. В варианте в компилятор строит VMT для каждого интерфейса, поддерживаемого классом. Главная хитрость в переключении на нужную VMT в момент приведения.
А теперь вернемся к Херону. Основное отличие — в определении класса нет списка интерфейсов, которые этот класс поддерживает. Попробуем теперь примерить существующие варианты. С вариантом а все просто — никаких проблем нет, разве что при резолвинге простым хешем не обойдешься. С вариантом б уже все намного печальнее. В отличие от дотнета, мы не можем гарантировать что на момент поднятия класса все реализуемые интерфейсы уже подняты. Т.е. компонентность в таком разе уже гарантированно идет лесом. Ну да бог с ней, теперь попробуем разобраться что можно придумать с компиляцией. А придумать тут можно только одно — надо проверить все присутствующие интерфейсы программы на возможность применения к каждому классу (см. приведенный пример, в котором компилятор не сможет понять какие интерфейсы к каким классам будут применены). Поскольку применимость любого интерфейса к любому классу штука неконтроллируемая, то и размер imap тоже будет неконтроллируемым. Особенно если появятся Java-style интерфейсты без членов. Ну и с вариантом в все ровно тоже самое, только в отличие от неконтроллируемого роста размеров imap получим его же в отношении количества VMT.
А вот теперь я с удовольствием послушаю, каким из этих способов пользуется Херон, а если никаким, то интересно описание четвертого варианта. Только без общих слов, конкретный алгоритм вызова метода в последних двух строчках примера.
Что же касается виртуальности/невиртуальности, то это вопрос качества компилятора и отказ от объявления интерфейса в классе ничего тут не меняет.
Ну и наконец, то что ты считаешь что наложение интерфейса на класс по совпадению сигнатур методов есть нечто новое, говорит о том что ты не очень хорошо представляешь себе что такое неявная реализация интерфейсов в дотнете.
CS>Этот неуклюжий боксинг
Да, боксинг конечно имеет прямое отношение к интерфейсам .
CS>, imap
Если речь идет о полиморфных вызовах, то какая то подобная механика нужна. Вобщем см. выше, надеюсь на этот раз я подробно расписал.
CS> и все остальное
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Оптимизация была сделана ранее: примитивные типы отделили от остальных, хотя на логическом уровне оставили их наследниками Object. Вот это и было оптимизацией. Боксинг — следствие принятой (для оптимизации) модели.
Паша, зуб даю, генезис боксинга совершенно иной. Не общий корень привел к боксингу, а наоборот, наличие автобоксинга позволило на логическом уровне ввести общий корень. Чтобы понять это достаточно взглянуть на Java, где никакого общего корня для value-типов отродясь не было, зато боксинг в определенном классе приложений присутствует в лучшем виде, только ручной (см. к примеру класс java.lang.Integer).
>> К примеру держишь ты в памяти строку из БД. А там и int, и float, и string, и byte[]. И хранить это нужно в одной коллекции. Вот тут тебе и придется либо боксить, либо приводить все к void*.
ПК>Верно. Но есть и другие случаи полиморфной работы с объектами разных типов, где можно было бы обойтись без боксинга.
Например?
ПК> В предложенной модели это организуется легко путем привязки интерфейсов в момент преобразования, а не в момент проектирования типов.
Здравствуйте, AndrewVK, Вы писали:
AVK>А в дотнете дженериками.
Две большие разницы как говорят в Одессе.
Generics and templates have a minor overlap in functionality. They both make it possible to create parameterized types which make it possible to create type safe collections. That’s where the similarity stops. First, let’s look at some features templates allow and how they are interpreted. I’ll assume that you know the syntax for templates.
Templates are instantiated at compile-time with the source code.
Templates are type safe.
Templates allow user-defined specialization.
Templates allow non-type parameters.
Templates use “lazy structural constraints”.
Templates support mix-ins. Templates are instantiated at compile-time. This is huge. Anyone who really wants to understand the limitations of either generics or templates needs to know this.
Здравствуйте, AndrewVK, Вы писали:
AVK>Теперь поговорим о реализации... VMT...
Забудь про VMT.
heron:interface/c++:templates это статическое связывание — нахождение адреса функции в compile/link time.
с#:interface это динамическое связывание — нахождение функции в runtime.
Я не знаю уже как это объяснить, я пасс. Anybody else?
Здравствуйте, c-smile, Вы писали:
CS>heron:interface/c++:templates это статическое связывание — нахождение адреса функции в compile/link time.
Значит приведенный в примере код с хероновскими интерфейсами работать не будет.
CS>с#:interface это динамическое связывание — нахождение функции в runtime.
Не обязательно. Я об этом несколько раз писал. Когда компилятор не может определить конкретную функцию, тогда да, в рантайме. А когда может, то в коде присутствует конкретный адрес (статический вызов).
CS>Я не знаю уже как это объяснить, я пасс. Anybody else?
Опять общие слова. Я тебе задал конкретный вопрос — можно ли применять хероновские интерфейсы в полиморфном коде. Если ответ нет — то и вопросов нет.
CS>Я думаю имеет смысл прочесть сравнение templates vs. generics.
Здравствуйте, c-smile, Вы писали:
AVK>>А в дотнете дженериками.
CS> Две большие разницы как говорят в Одессе.
А я и не говорю что это одно и то же. И, эта, не надо мне рассказывать что такое дженерики, я на них еще года полтора назад очень внимательно смотрел. Более того, я знаю причины отличий дженериков и шаблонов. Вот только никакого отношения к боксингу это не имеет.
AVK>Это ты похоже чего то непонимаешь. Еще раз пример, более подробно: AVK>
AVK>class Base {}
AVK>interface IA
AVK>{
AVK> void Foo();
AVK>}
AVK>class X : Base, IA
AVK>{
AVK> void Bar1(); // адрес метода в памяти 0001
AVK> void Foo(); // адрес метода в памяти 0002
AVK>}
AVK>class Y : Base, IA
AVK>{
AVK> void Foo(); // адрес метода в памяти 0003
AVK> void Bar2(); // адрес метода в памяти 0004
AVK>}
AVK>...
AVK>Base[] bs = new Base[]{new X(). new Y()};
AVK>(IA)bs[0].Foo(); // здесь должен быть вызов по адресу 0002
AVK>(IA)bs[1].Foo(); // а здесь должен быть вызов по адресу 0003
AVK>
Heron:
class X {
void Bar1();
void Foo();
}
class Y {
void Bar2();
void Foo();
}
interface IFoo
{
void Foo();
}
IFoo foos[] = { new X(), new Y() }
/* в этой строке произойдет построение локальных интерфейсных
таблиц IFoo для классов X и Y и ссылки на них сохранятся в экземлярах IFoo */
foos[0].Foo();
foos[1].Foo();
Как ты можешь заметить VMT (виртуальное наследование) здесь не нужны.
Т.е. Base тебе не нужен в принципе.
Объявляя IFoo ты просто говоришь: мне нужны адреса объектов и адреса их методов Foo из разных классов.
Здравствуйте, c-smile, Вы писали:
CS>Как ты можешь заметить VMT (виртуальное наследование) здесь не нужны.
Какой ты хитрый. Я тебя не спрашиваю как мне полиморфный код сделать неполиморфным. Для примера делать это бессмысленно, поскольку это игрушечный код. Убери к примеру из одного из классов метод Foo () (и вызов его соотв.), а в контейнере класс оставь, и все, приехали. Я тебя спрашивал как в Хероне будет реализован тот код что я привел. Если не знаешь как, так и скажи, а не увиливай от ответа.
CS>Т.е. Base тебе не нужен в принципе.
Я сам буду решать, что мне нужно. Вот к примеру типичный вариант использования интерфейсов.
interface ISupportInitialize
{
void Initialize();
}
class Control {}
class SomeCustomControl : Control, ISupportInitialize
{
public void Initialize();
}
...
foreach (ControlDescription cd in ControlDescriptions)
{
Control c = CreateControl(cd);
ISupportInitialize isi = c as ISupportInitialize;
if (isi != null)
isi.Initialize();
}
Здравствуйте, AndrewVK, Вы писали:
AVK>Какой ты хитрый. Я тебя не спрашиваю как мне полиморфный код сделать неполиморфным. Для примера делать это бессмысленно, поскольку это игрушечный код. Убери к примеру из одного из классов метод Foo () (и вызов его соотв.), а в контейнере класс оставь, и все, приехали.
А правда, что будет? Я так полагаю, что просто компилятор заругается вот в этой строке:
IFoo foos[] = { new X(), new Y() }
Аналогично тому, как C++ ругается на попытку инстанциировать и использовать шаблон с аргументом, у которого чего-то не хватает.
McSeem
Я жертва цепи несчастных случайностей. Как и все мы.
Здравствуйте, McSeem2, Вы писали:
MS>Здравствуйте, AndrewVK, Вы писали:
MS>А правда, что будет? Я так полагаю, что просто компилятор заругается вот в этой строке:
MS>
MS>IFoo foos[] = { new X(), new Y() }
MS>
MS>Аналогично тому, как C++ ругается на попытку инстанциировать и использовать шаблон с аргументом, у которого чего-то не хватает.
Да именно так и будет. Т.е. IFoo интерфейс не может быть построен для класса у которого
нет функции Foo. Иными словами : класс Z не удовлетворяет спецификации интерфейса IW.
Собственно heron:interface это есть описание опять же некоего шаблона которому должен
удовлетворять класс.
template в C++ делает примерно то же самое но неявно. Эта вот неявность например
приводит к логическим конфликтам когда два метода int Foo() и void Foo() неразличимы для механизма C++:templates
template<typename T>
void Bar(T& t) {
t.Foo(); // здесь проблема - какую Foo подставлять.
}
С interface было бы проще — там можно четко указать сигнатуру. Т.е.
interface IW
{
int Foo();
}
void Bar(IW t)
{
t.Foo(); // здесь проблемы нет
}
Здравствуйте, McSeem2, Вы писали:
MS>Я что-то запутался, какой конкретно "исходный пример" имеется в виду. Можно его еще раз в студию?
class Base {}
interface IA
{
void Foo();
}
class X : Base, IA
{
void Bar1(); // адрес метода в памяти 0001void Foo(); // адрес метода в памяти 0002
}
class Y : Base, IA
{
void Foo(); // адрес метода в памяти 0003void Bar2(); // адрес метода в памяти 0004
}
...
Base[] bs = new Base[]{new X(). new Y()};
(IA)bs[0].Foo(); // здесь должен быть вызов по адресу 0002
(IA)bs[1].Foo(); // а здесь должен быть вызов по адресу 0003
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, c-smile, Вы писали:
CS>>Как ты можешь заметить VMT (виртуальное наследование) здесь не нужны.
AVK>Какой ты хитрый. Я тебя не спрашиваю как мне полиморфный код сделать неполиморфным. Для примера делать это бессмысленно, поскольку это игрушечный код. Убери к примеру из одного из классов метод Foo () (и вызов его соотв.), а в контейнере класс оставь, и все, приехали. Я тебя спрашивал как в Хероне будет реализован тот код что я привел. Если не знаешь как, так и скажи, а не увиливай от ответа.
Это не я хитрый, а автор Heron'a умный.
AVK>Я сам буду решать, что мне нужно. Вот к примеру типичный вариант использования интерфейсов.
Ничего ты не можешь решать. Так как у тебя есть один единственный спопособ — с помощью vmt/imap.
Статический полиморфизм тебе не дан большими дядями.
Это я могу решать в C++ что мне выбрать — template или virtual полиморфизм.
Если еще при этом будет механизм этих interfaces (который представляется родным для C++) будет еще лучше.
Т.е. для примера предлагаемого тобой (ниже) я могу выбрать наиболее оптимальное и эффективное решение.
Могу построить это все как коллекцию интерфейсных ссылок, могу пойти по сугубо виртуальному пути,
а могу построить нечто посредине.
AVK>
AVK>interface ISupportInitialize
AVK>{
AVK> void Initialize();
AVK>}
AVK>class Control {}
AVK>class SomeCustomControl : Control, ISupportInitialize
AVK>{
AVK> public void Initialize();
AVK>}
AVK>...
AVK>foreach (ControlDescription cd in ControlDescriptions)
AVK>{
AVK> Control c = CreateControl(cd);
AVK> ISupportInitialize isi = c as ISupportInitialize;
AVK> if (isi != null)
AVK> isi.Initialize();
AVK>}
AVK>
AVK>class Base {}
AVK>interface IA
AVK>{
AVK> void Foo();
AVK>}
AVK>class X : Base, IA
AVK>{
AVK> void Bar1(); // адрес метода в памяти 0001
AVK> void Foo(); // адрес метода в памяти 0002
AVK>}
AVK>class Y : Base, IA
AVK>{
AVK> void Foo(); // адрес метода в памяти 0003
AVK> void Bar2(); // адрес метода в памяти 0004
AVK>}
AVK>...
AVK>Base[] bs = new Base[]{new X(). new Y()};
AVK>(IA)bs[0].Foo(); // здесь должен быть вызов по адресу 0002
AVK>(IA)bs[1].Foo(); // а здесь должен быть вызов по адресу 0003
AVK>
Понятно. В рамках херона (или как его там), ошибка будет здесь:
AVK>
AVK>(IA)bs[0].Foo(); // здесь должен быть вызов по адресу 0002
AVK>(IA)bs[1].Foo(); // а здесь должен быть вызов по адресу 0003
AVK>
Причем, вне зависимости от каких-либо X,Y. Поскольку Base не имеет сигнатур, описанных в IA, то и использование Base в качестве интерфеса становится невалидным. Это статический, compile time полиморфизм, почти то же что и шаблоны, но с более строгой спецификацией сигнатур интерфейсных функций.
А, кстати, что произойдет в C# в описанной ситуации (у класса X удалили void Foo())?
Я имею в виду — кто эту бяку обнаружит?
McSeem
Я жертва цепи несчастных случайностей. Как и все мы.
Здравствуйте, c-smile, Вы писали:
CS>Т.е. ты утверждаешь что вот это вот валидная конструкция?
Забавно . Нет. Неужели ты не понял о чем речь? Еще раз задаю вопрос — будет ли в Хероне работать мой пример без изменений? И если будет то каким образом?