Косяк во всем CLI (Common Language Infrastructure)!
От: Pavel Chikulaev Россия  
Дата: 15.12.05 12:24
Оценка:
Здравствуйте, Oyster, Вы писали:

O>А нефиг в Finalize обращаться к таким ресурсам — он для освобождения чего-то, что ещё не освобождено, предназначен. И вообще — Dispose лучше вызывать из родительского Dispose, т.к. из Finalize имхо уже немного поздно это делать (курим Dispose pattern).

Это только пример. Я все прекрасно понимаю.

O>В общем, если проектировать правильно, то никаких гвоздей


Это все вершина айсберга... копни по глубже... ИМХО косяк во всем CLI...

17.12.05 05:17: Ветка выделена из темы C++ vs C# and C++/CLI :) Dynamic Type
Автор: Pavel Chikulaev
Дата: 15.12.05
— VladD2
Re[3]: Косяк во всем CLI (Common Language Infrastructure)!
От: Oyster Украина https://github.com/devoyster
Дата: 15.12.05 12:27
Оценка: +1
Здравствуйте, Pavel Chikulaev, Вы писали:

PC>Это все вершина айсберга... копни по глубже... ИМХО косяк во всем CLI...


Нет уж, извольте. Если метод виртуальный, то логично, что он всегда вызывается через vtbl. Иначе дизайн C# стал бы более ... неочевидным, что ли.

Кстати, причём тут CLI? В данном случае именно компилятор вполне может вставить call вместо callvirt, если вызов происходит из "деструктора" (из Finalize). Только вот ихмо это было бы неправильно.

Если ты об этом, конечно.
Re[4]: Косяк во всем CLI (Common Language Infrastructure)!
От: Pavel Chikulaev Россия  
Дата: 15.12.05 12:40
Оценка: :)
Здравствуйте, Oyster, Вы писали:

O>Здравствуйте, Pavel Chikulaev, Вы писали:


PC>>Это все вершина айсберга... копни по глубже... ИМХО косяк во всем CLI...


O>Нет уж, извольте. Если метод виртуальный, то логично, что он всегда вызывается через vtbl. Иначе дизайн C# стал бы более ... неочевидным, что ли.


O>Кстати, причём тут CLI? В данном случае именно компилятор вполне может вставить call вместо callvirt, если вызов происходит из "деструктора" (из Finalize). Только вот ихмо это было бы неправильно.


O>Если ты об этом, конечно.


ИМХО

В CLI нет ООП. А именно:
Пусть у нас есть Base и Derived : Base. В CLI отношения "is-a" в момент разрушения/удаления/собирания объектов, т.к. подобъект Base не является Base, Derived части уже нет, а объект все еще является Derived, и соответственно виртуальные функции могут обращаться к данным которых уже нет.

Вопрос:
Зачем так сделали? Я просто не понимаю...
Re[5]: Косяк во всем CLI (Common Language Infrastructure)!
От: Oyster Украина https://github.com/devoyster
Дата: 15.12.05 12:46
Оценка:
Здравствуйте, Pavel Chikulaev, Вы писали:

PC>ИМХО


Это точно

PC>В CLI нет ООП. А именно:

PC>Пусть у нас есть Base и Derived : Base. В CLI отношения "is-a" в момент разрушения/удаления/собирания объектов, т.к. подобъект Base не является Base, Derived части уже нет, а объект все еще является Derived, и соответственно виртуальные функции могут обращаться к данным которых уже нет.

В CLI нет понятия "подобъект", а есть понятие "подкласс". Объект физически один (а не два — типа Base и типа Derived) и в нём находятся все поля — и базового класса, и производного. Соответственно, часть объекта удалить не получится (да и неправильно это будет — к примеру, не забываем про public/protected поля, доступные наследникам). И я, например, не считаю это "нарушением" ООП.

PC>Вопрос:

PC>Зачем так сделали? Я просто не понимаю...

Потому что в C# (и CLI вообще) наследование реализовано не через аггрегацию объекта базового типа

Главное — следить за дизайном своего кода. Тогда проблем не будет.
Re[6]: Косяк во всем CLI (Common Language Infrastructure)!
От: Pavel Chikulaev Россия  
Дата: 15.12.05 13:01
Оценка:
Здравствуйте, Oyster, Вы писали:

O>В CLI нет понятия "подобъект", а есть понятие "подкласс". Объект физически один (а не два — типа Base и типа Derived) и в нём находятся все поля — и базового класса, и производного. Соответственно, часть объекта удалить не получится (да и неправильно это будет — к примеру, не забываем про public/protected поля, доступные наследникам). И я, например, не считаю это "нарушением" ООП.


Но ведь:
class Base
{
    ~Base { ... }
    public virtual void foo() { ... };
}

class Derived : Base
{
    ~Derived() { ... }
    public void overide foo() { ... };
    
    SomeClass someClass;
}


Пусть у нас есть
Base b = new Derived;

И в какой-то момент происходит зачистка объекта (Псевдокод):
вызов b.Finalize() который вызвает
    Derived::~Derived()
    gcrootов на someClass нету (или есть?), и он возможно тоже зачищается.
    Base::~Base() который вызывает виртуальную функцию foo. Но почему-то она не меняется на Base::foo.


Я совсем не прав?

O>Потому что в C# (и CLI вообще) наследование реализовано не через аггрегацию объекта базового типа

Ссылку можна в ecma 335? А как?
Re[7]: Косяк во всем CLI (Common Language Infrastructure)!
От: Oyster Украина https://github.com/devoyster
Дата: 15.12.05 13:09
Оценка:
Здравствуйте, Pavel Chikulaev, Вы писали:

PC>... Я совсем не прав?


Ты перепутал местами строчки. Скорее, так:

gcrootов на someClass нету, и он возможно тоже зачищается.
вызов b.Finalize() который вызвает
  код внутри Derived::~Derived() ("деструктор" на самом деле компилируется в Finalize)
  Base::Finalize() который вызывает виртуальную функцию foo. Но она не меняется на Base::foo, потому что вызов виртуальный

Вызов Finalize означает, что объект (и, соответственно, объекты, которые достижимы только из него) уже никем не держатся и могут очищаться. И никто тебе не гарантирует, что b.Finalize() вызовется раньше, чем someClass.Finalize().

PC>Ссылку можна в ecma 335? А как?


Зачем ссылку? Открой Derived рефлектором и увидишь, что наследник просто содержит все поля базового класса. Кстати, а в C++ разве иначе?
Re[7]: Косяк во всем CLI (Common Language Infrastructure)!
От: Oyster Украина https://github.com/devoyster
Дата: 15.12.05 13:11
Оценка:
Здравствуйте, Pavel Chikulaev, Вы писали:

PC>... Я совсем не прав?


Ещё одно замечание — в CLI нет деструкторов, есть только метод Finalize(), предназначенный для освобождения ресурсов. Этот метод вызывается GC; соответственно, момент удаления объекта не определён (как в C++, например), равно как и порядок вызова Finalize для объектов.
Re[8]: Косяк во всем CLI (Common Language Infrastructure)!
От: Pavel Chikulaev Россия  
Дата: 15.12.05 13:17
Оценка:
Здравствуйте, Oyster, Вы писали:

O>Здравствуйте, Pavel Chikulaev, Вы писали:


PC>>... Я совсем не прав?


O>Ты перепутал местами строчки. Скорее, так:

gcroot на someclass есть, пока не вызван finalizer (или ошибаюсь?)

O>
O>gcrootов на someClass нету, и он возможно тоже зачищается.
O>вызов b.Finalize() который вызвает
O>  код внутри Derived::~Derived() ("деструктор" на самом деле компилируется в Finalize) 
знаю
O>  Base::Finalize() который вызывает виртуальную функцию foo. Но она не меняется на Base::foo, потому что вызов виртуальный
O>

Почему не меняют адрес виртуальной функции? Эт не правильно Derived части ж нет....

O>Вызов Finalize означает, что объект (и, соответственно, объекты, которые достижимы только из него) уже никем не держатся и могут очищаться. И никто тебе не гарантирует, что b.Finalize() вызовется раньше, чем someClass.Finalize().

Да я знаю Давай в Derived::~Derived() напишу someClass.Dispose()... тогда как?

PC>>Ссылку можна в ecma 335? А как?


O>Зачем ссылку? Открой Derived рефлектором и увидишь, что наследник просто содержит все поля базового класса.

Эт реализация, мне надо общее, стандартное. (Я из форума С++, а там любят стандарт и ссылки).

O>Кстати, а в C++ разве иначе?

Implementation-defined
Re[8]: Косяк во всем CLI (Common Language Infrastructure)!
От: Pavel Chikulaev Россия  
Дата: 15.12.05 13:20
Оценка:
Здравствуйте, Oyster, Вы писали:

O>Здравствуйте, Pavel Chikulaev, Вы писали:


PC>>... Я совсем не прав?


O>Ещё одно замечание — в CLI нет деструкторов, есть только метод Finalize(), предназначенный для освобождения ресурсов. Этот метод вызывается GC; соответственно, момент удаления объекта не определён (как в C++, например), равно как и порядок вызова Finalize для объектов.

Я нигде не сказал слово дтор, да и знаю я это!
Re[9]: Косяк во всем CLI (Common Language Infrastructure)!
От: Oyster Украина https://github.com/devoyster
Дата: 15.12.05 13:37
Оценка: :)
Здравствуйте, Pavel Chikulaev, Вы писали:

PC>gcroot на someclass есть, пока не вызван finalizer (или ошибаюсь?)


Ошибаешься. Поскольку на сам родительский класс ссылок нету, то и на someClass их уже неду (из объектов, на которые никто не ссылается, ссылки не считаются). Почитал бы про GC
Автор(ы): Игорь Ткачев
Дата: 06.12.2002
Алгоритм работы сборщика мусора (garbage collector, далее просто GC), являющегося частью CLR, подробно описан в книге Джефри Рихтера (Jeffrey Richter) «Applied Microsoft .NET Framework Programming». Мы не будем приводить здесь столь же подробное описание этого алгоритма, но обязательно остановимся на некоторых ключевых моментах.
.

PC>Почему не меняют адрес виртуальной функции? Эт не правильно Derived части ж нет....


Опять ошибаешься — она есть, т.к. объект собирается не по частям, а целиком.

В общем, дьявол в дизайне Например, я могу написать так:

class Base
{
    public string X = "Hello!";
    
    ~Base()
    {
        int y = X.Length;
    }
}

class Derived : Base
{
    ~Derived()
    {
        X = null;
    }
}

Что дальше?

PC>Да я знаю Давай в Derived::~Derived() напишу someClass.Dispose()... тогда как?


Ну так писать нельзя — я уже говорил, по-моему, т.к. в этот момент Finalize() уже мог быть вызван для someClass и неизвестно, что у него там с состоянием. Повторюсь — хорошенько почитай про Dispose Pattern и пойми, что чужой Dispose из Finalize вызывать нехорошо, т.к. из Finalize освобождаются только unmanaged ресурсы.

PC>Эт реализация, мне надо общее, стандартное. (Я из форума С++, а там любят стандарт и ссылки).


Тут я промолчу, т.к. не читал стандарт.

PC>Implementation-defined


Ужас
Re[10]: Косяк во всем CLI (Common Language Infrastructure)!
От: Pavel Chikulaev Россия  
Дата: 15.12.05 13:46
Оценка:
Здравствуйте, Oyster, Вы писали:

[snipped]
Ок. Тогда попробуй объяснить почему не происходит смена динамического типа после вызова Derived::Finalize и/или Derived::IDisposable::Dispose ? Где логика. Некоторые Derived части могут быть измененены\удалены...

PC>>Да я знаю Давай в Derived::~Derived() напишу someClass.Dispose()... тогда как?


O>Ну так писать нельзя — я уже говорил, по-моему, т.к. в этот момент Finalize() уже мог быть вызван для someClass и неизвестно, что у него там с состоянием. Повторюсь — хорошенько почитай про Dispose Pattern и пойми, что чужой Dispose из Finalize вызывать нехорошо, т.к. из Finalize освобождаются только unmanaged ресурсы.

Только для примера!!!

PC>>Эт реализация, мне надо общее, стандартное. (Я из форума С++, а там любят стандарт и ссылки).

O>Тут я промолчу, т.к. не читал стандарт.
http://www.ecma-international.org/publications/standards/Ecma-335.htm CLI
http://www.ecma-international.org/publications/standards/Ecma-334.htm C#
http://www.ecma-international.org/publications/standards/Ecma-372.htm C++/CLI
Re[11]: Косяк во всем CLI (Common Language Infrastructure)!
От: Oyster Украина https://github.com/devoyster
Дата: 15.12.05 14:10
Оценка:
Здравствуйте, Pavel Chikulaev, Вы писали:

PC>Ок. Тогда попробуй объяснить почему не происходит смена динамического типа после вызова Derived::Finalize и/или Derived::IDisposable::Dispose ? Где логика. Некоторые Derived части могут быть измененены\удалены...


Ну IDisposable тут вообще ни при чём, т.к. этот интерфейс не является частью рантайма и никоим образом не используется GC (только дёргается из кода напрямую), так что его не трогаем. Note: C++-ник может рассматривать IDisposable как некую замену RAII (грубо говоря) — мы используем IDisposable, когда нам надо обязательно отпустить ресурс по выходу из некоторого блока, а не ждать GC.

А вызов Finalize(), вообще говоря, не гарантирует обязательной очистки состояния некоей части объекта — вроде ж ты понимаешь, что это не деструктор... А поскольку не гарантирует, то мы и не можем быть уверены, что "состояние наследника" полностью очищено и, соответственно, ни о какой "смене динамического типа" не может быть и речи. Как итог, синтаксис языка становится проще (очевиднее), а на программиста ложится больше ответственности в плане дизайна (когда её было мало?).

PC>Только для примера!!!


У меня тоже был пример плохого дизайна, но ведь CLI не виноват в том, что программист педалит плохой дизайн, правда?

PC>http://www.ecma-international.org/publications/standards/Ecma-335.htm CLI


Цитата оттуда специально для тебя (Chapter 8.9.8, p. 41):

The derived class type shall support all of the supported interfaces contracts, class contracts, event contracts, method contracts, and property contracts of its base type. In addition, all of the locations defined by the base type are also defined in the derived type.


И вот ещё цитата (Chapter 8.3, p. 22), чтобы было понятно, что в данном случае location == field:

Values are stored in locations.


Это по поводу "в C# (и CLI вообще) наследование реализовано не через аггрегацию объекта базового типа". Повторюсь — в CLI производный тип просто включает все поля базового.
Re[12]: Косяк во всем CLI (Common Language Infrastructure)!
От: Pavel Chikulaev Россия  
Дата: 15.12.05 14:15
Оценка:
Здравствуйте, Oyster, Вы писали:

[snipped]
Очевиднее?

Ок. Почему только не ясно. Сообственно это и был исходный вопрос. И чем агрегация базового в наследнике плоха?
Re[13]: Косяк во всем CLI (Common Language Infrastructure)!
От: Oyster Украина https://github.com/devoyster
Дата: 15.12.05 14:29
Оценка: 6 (1) -1
Здравствуйте, Pavel Chikulaev, Вы писали:

PC>[snipped]

PC>Очевиднее?

Я полагаю, аргументы исчерпались?

Для меня очевиднее потому, что ... (см. ниже) Для тебя неочевиднее потому, что ты просто не привык к этому.

PC>Ок. Почему только не ясно. Сообственно это и был исходный вопрос.


Ну я же уже объяснял, почему. Сейчас постараюсь сформулировать более сжато:

  1. В CLI у типов нет понятия деструктора. Объекты уничтожает GC и только GC. Метод Finalize() предназначен только для освобождения unmanaged ресурсов, поэтому мы не можем утверждать, что к моменту вызова Base.Finalize() всё состояние, добавленное типом Derived, будет освобождено.
  2. Наследование всегда реализовано не через ... удержание ссылки на объект базового типа, поэтому семантика удаления объекта совершенно иная (только всем куском).
  3. Виртуальный метод всегда вызывается через vtbl, откуда бы он ни был вызван (иначе имела бы место путаница, т.к. Finalize — самый что ни на есть обычный метод).

Я не гуру CLI, поэтому объяснил как смог.

PC>И чем агрегация базового в наследнике плоха?


А чем она хороша, особенно в managed environment? Иметь 10 объектов в куче вместо одного — просто замечательная идея Ещё и косвенные вызовы появляются — тоже супер. И я просто не знаю, как бы в таком случае работал reflection или сериализация...

В общем, зачем усложнять там, где можно сделать просто?
Re[14]: Косяк во всем CLI (Common Language Infrastructure)!
От: Cyberax Марс  
Дата: 15.12.05 14:42
Оценка: 3 (1) -1
Oyster wrote:
> 1. В CLI у типов нет понятия деструктора. Объекты уничтожает GC и
> только GC. Метод Finalize() предназначен только для освобождения
> unmanaged ресурсов, поэтому мы не можем утверждать, что к моменту
> вызова Base.Finalize() всё состояние, добавленное типом Derived,
> будет освобождено.
Замените в примере Finalize на Dispose и подумайте как нормально
организовать иерархию Dispose'ов.

Hint: у Dispose семантика фактически такая же, как у С++ного деструктора.

> PC>И чем агрегация базового в наследнике плоха?

> А чем она хороша, особенно в managed environment? Иметь 10 объектов в
> куче вместо одного — просто замечательная идея Ещё и косвенные вызовы
> появляются — тоже супер.
Это бред. В С++ объекты (даже со сложным деревом множественного
наследования) выделяются одним куском. Лишней косвенности тоже непонятно
откуда взяться.

> В общем, зачем усложнять там, где можно сделать просто?

Потому как сложные вещи тогда будет делать очень сложно.

--
С уважением,
Alex Besogonov (alexy@izh.com)
Posted via RSDN NNTP Server 2.0
Sapienti sat!
Re[15]: Косяк во всем CLI (Common Language Infrastructure)!
От: Pavel Chikulaev Россия  
Дата: 15.12.05 14:53
Оценка: +1 :)
Здравствуйте, Cyberax, Вы писали:

C>Замените в примере Finalize на Dispose и подумайте как нормально

C>организовать иерархию Dispose'ов.

C>Hint: у Dispose семантика фактически такая же, как у С++ного деструктора.

Dispose не часть CLI, и из-за него динамическую смену типа засовывать не будут...

>> В общем, зачем усложнять там, где можно сделать просто?

C>Потому как сложные вещи тогда будет делать очень сложно.
Мне тоже не нравится, но похоже в CLI лучше так... Там и vtbl инициализируются до входа в ктор
class Base
{
    Base() { foo(); }
    public virtual void foo () { Console.WriteLine("Base.foo"); }
}

class Derived : Base
{
    public void overide foo() { Console.WriteLine("Derived.foo"); }
}

void bar()
{
    Derived d = new Derived; //выведет Derived.foo :no:
}


Да исключения дурацкие

Кол-во заморочек C++ < кол-во заморочек C# + CLI следовательно С++ проще (ерунда это конечно)

Re[15]: Косяк во всем CLI (Common Language Infrastructure)!
От: Oyster Украина https://github.com/devoyster
Дата: 15.12.05 14:58
Оценка: 23 (1) +1 :)
Здравствуйте, Cyberax, Вы писали:

C>Замените в примере Finalize на Dispose и подумайте как нормально

C>организовать иерархию Dispose'ов.

C>Hint: у Dispose семантика фактически такая же, как у С++ного деструктора.


Совершенно не такая же — Dispose не удаляет объект (вот оно — главное отличие), он освобождает (или не освобождает) ресурсы, и никаких гарантий нам вызов Dispose в общем случае не даёт.

C>Это бред. В С++ объекты (даже со сложным деревом множественного

C>наследования) выделяются одним куском. Лишней косвенности тоже непонятно
C>откуда взяться.

А вот и не бред

На данный момент в CLI объекты reference-типа хранятся в куче. Каждый объект хранится в куче отдельно. Предложенная модификация повлечёт за собой изменение способа конструирования/хранения/сбора объектов — оно надо?

Насчёт лишней косвенности:

// "Обычное" наследование
class Base
{
    virtual void M()
    {
    }
}

class Derived : Base
{
    virtual void N()
    {
    }
}

// Агрегация
class Base
{
    virtual void M()
    {
    }
}

class Derived
{
    Base b;
    
    // Интерфейс Base как бы надо сохранить
    virtual void M()
    {
        // Насчёт дополнительных косвенных вызовов
        b.M();
    }
    virtual void N()
    {
    }
}

Да, есть JIT, который может заинлайнить вызов. А может и не заинлайнить.

В общем, такую штуку можно использовать при реализации mix-ins в C# (тогда код будет явно декларировать свои намерения по агрегации), например, но не для реализации обычного наследования — это точно.

C>Потому как сложные вещи тогда будет делать очень сложно.


Точно — чего-то мне smart pointers в C# не хватает

Дело в том, что в C# сложные вещи можно делать проще и чего-то совершенно не тянет делать их очень сложно

PS: ты забыл предложить перенести BOOST на C# А то как же тут без него?
Re[16]: Косяк во всем CLI (Common Language Infrastructure)!
От: Pavel Chikulaev Россия  
Дата: 15.12.05 15:04
Оценка: +1
Здравствуйте, Oyster, Вы писали:

C>>Hint: у Dispose семантика фактически такая же, как у С++ного деструктора.

O>Совершенно не такая же — Dispose не удаляет объект (вот оно — главное отличие), он освобождает (или не освобождает) ресурсы, и никаких гарантий нам вызов Dispose в общем случае не даёт.
+1

C>>Это бред. В С++ объекты (даже со сложным деревом множественного

C>>наследования) выделяются одним куском. Лишней косвенности тоже непонятно
C>>откуда взяться.
O>А вот и не бред

O>На данный момент в CLI объекты reference-типа хранятся в куче. Каждый объект хранится в куче отдельно. Предложенная модификация повлечёт за собой изменение способа конструирования/хранения/сбора объектов — оно надо?

Чуть изменить GCHandle и все будет легко...

O>Насчёт лишней косвенности:

O>
O>class Derived
O>{
O>    Base b;
    
O>    // Интерфейс Base как бы надо сохранить
O>    virtual void M()
O>    {
O>        // Насчёт дополнительных косвенных вызовов
O>        b.M();
O>    }
O>    virtual void N()
O>    {
O>    }
O>}
O>

Ужас... А и не знал, что у меня в С++ такая косвенность....

O>Точно — чего-то мне smart pointers в C# не хватает

RAII, smart_ptr лишь способ

O>Дело в том, что в C# сложные вещи можно делать проще и чего-то совершенно не тянет делать их очень сложно

Ага std::set как?

O>PS: ты забыл предложить перенести BOOST на C# А то как же тут без него?

язык простой слишком
Re[17]: Косяк во всем CLI (Common Language Infrastructure)!
От: Oyster Украина https://github.com/devoyster
Дата: 15.12.05 15:48
Оценка: :)
Здравствуйте, Pavel Chikulaev, Вы писали:

PC>Ага std::set как?


PowerCollections. В FCL, как и в C++ Standard Library, не всё имеется изначально.

PC>язык простой слишком


Re[16]: Косяк во всем CLI (Common Language Infrastructure)!
От: Cyberax Марс  
Дата: 15.12.05 18:41
Оценка: +2
Oyster wrote:
> C>Hint: у Dispose семантика фактически такая же, как у С++ного деструктора.
> Совершенно не такая же — Dispose *не удаляет объект* (вот оно — главное
> отличие) он освобождает (или не освобождает) ресурсы, и никаких
> гарантий нам вызов Dispose в общем случае не даёт.
В С++ деструктор тоже не удаляет объект (этим занимается operator
delete), а освобождает связанные с объектом ресурсы. Именно этим же и
занимается Dispose.

> На данный момент в CLI объекты reference-типа хранятся в куче. Каждый

> объект хранится в куче отдельно. Предложенная модификация повлечёт за
> собой изменение способа конструирования/хранения/сбора объектов — оно надо?
Может быть. Хотя может и стоит изменить семантику хранения.

> class Derived

> {
> Base b;
>
> // Интерфейс Base как бы надо сохранить
> virtual void M()
> {
> // Насчёт дополнительных косвенных вызовов
> b.M();
Ничего особенного не будет. vtbl'и в случае одинарного наследования
будут расположены "вплотную", а значит и вызов будет обычным вызовом по
указателю.

Это легче всего описать на чистом С:
struct base
{
    void (*func1)();
    void (*func2)();
};
struct derived
{
    struct base base_struct;
    void (*func3)();
    void (*func4)();
};

Структуры будт расположены "вплотную", так что никаких затрат не будет.

Вот с множественным наследованием (и интерфейсам) все сложнее.

--
С уважением,
Alex Besogonov (alexy@izh.com)
Posted via RSDN NNTP Server 2.0
Sapienti sat!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.