Почему структуры не поддерживают наследование?

Я думаю, что существует своего рода элитизм БАКАЛАВРА НАУК, который происходит, когда люди говорят о XNA или платформах java игры или pygame или что бы то ни было. Да, профессиональные игровые здания будут использовать C++ в течение долгого, долгого времени. Но Atmospherian спрашивает о чем-то для его собственного проекта, таким образом, я собираюсь предположить, что он - любитель (если бы он был генеральным директором Эпопеи или чего-то, что я сомневаюсь, что он отправил бы на ТАК)

Так или иначе, просто потому что Вы не могли кодировать FarCry в нем, не означает, что это - бесполезная платформа. Игры уровня главной студии требуют миллионов долларов и десятков людей, но XNA/Java/pygame/etc позволили любителям делать некоторый довольно потрясающий материал. Любительские игры и игры главной студии являются двумя различными зверями и призывают к двум различным наборам инструментов.

Для ответа на вопросы, Atmospherian:

- Да, XNA может сделать, некоторый довольно интересный материал и люди использовали его для создания некоторых прохладных игр.

- Если Вы хотите, чтобы XNA был кросс-платформенным и работал с моно, я думаю, что это технически возможно, но я не задержал бы дыхание.

- Если Вы хотите закончить с заданием в игровой промышленности, Вы собираетесь должны изучить C++.

-I думают, что путь наименьшего сопротивления к получению рабочего межплатформенного игрового механизма в настоящее время находится в поросенок . Простота использования могла бы обменять со скоростью при сравнении с Java или C++.

120
задан Carl Manaster 3 August 2009 в 22:26
поделиться

10 ответов

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

Проблема в том, что по причинам производительности и GC массивы типов значений хранятся «встроенными». Например, для new FooType [10] {...} , если FooType является ссылочным типом, в управляемой куче будет создано 11 объектов (один для массива и 10 для каждого экземпляра типа). Если FooType вместо этого является типом значения, в управляемой куче будет создан только один экземпляр - для самого массива (поскольку каждое значение массива будет храниться «встроенным в массив»).

Теперь , предположим, у нас есть наследование с типами значений. В сочетании с описанным выше поведением массивов «встроенное хранилище» происходят плохие вещи, как можно увидеть в C ++ .

Рассмотрим этот псевдокод C #:

struct Base
{
    public int A;
}

struct Derived : Base
{
    public int B;
}

void Square(Base[] values)
{
  for (int i = 0; i < values.Length; ++i)
      values [i].A *= 2;
}

Derived[] v = new Derived[2];
Square (v);

По обычным правилам преобразования Derived [] может быть преобразован в Base [] (лучше или хуже), поэтому, если вы s / struct / class / g для приведенный выше пример, он скомпилируется и запустится, как ожидалось, без проблем. Но если Base и Derived являются типами значений, а массивы хранят значения встроенными, тогда у нас есть проблема.

У нас проблема, потому что Square () ничего не знает о Derived , он будет использовать только арифметику указателя для доступа к каждому элементу массива, увеличивая его на постоянную величину ( sizeof (A) ). Сборка будет примерно такой:

for (int i = 0; i < values.Length; ++i)
{
    A* value = (A*) (((char*) values) + i * sizeof(A));
    value->A *= 2;
}

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

Итак, если бы это действительно произошло, у нас были бы проблемы с повреждением памяти. В частности, в Square () , значения [1] .A * = 2 будут фактически изменить значения [0] .B !

Попробуйте отладить ТО !

119
ответ дан 24 November 2019 в 01:39
поделиться

Представьте, что структуры поддерживают наследование. Затем объявление:

BaseStruct a;
InheritedStruct b; //inherits from BaseStruct, added fields, etc.

a = b; //?? expand size during assignment?

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

Еще лучше, рассмотрите следующее:

BaseStruct[] baseArray = new BaseStruct[1000];

baseArray[500] = new InheritedStruct(); //?? morph/resize the array?
68
ответ дан 24 November 2019 в 01:39
поделиться

Структуры не используют ссылки (если они не заключены в рамку, но вы должны попытаться избежать этого), поэтому полиморфизм не имеет смысла, поскольку нет косвенного обращения через указатель ссылки. Объекты обычно находятся в куче и на них ссылаются через ссылочные указатели, но структуры выделяются в стеке (если они не помещены в коробку) или выделяются «внутри» памяти, занимаемой ссылочным типом в куче.

14
ответ дан 24 November 2019 в 01:39
поделиться

Вот что сказано в документации :

Структуры особенно полезно для небольших структур данных, имеющих семантику значений. Комплексные числа, точки в системе координат или пары ключ-значение в словаре - все это хорошие примеры структур. Ключом к этим структурам данных является то, что у них мало членов данных, что они не требуют использования наследования или ссылочной идентичности и что они могут быть удобно реализованы с использованием семантики значений, когда присваивание копирует значение вместо ссылки.

В основном, предполагается, что они хранят простые данные и поэтому не имеют «дополнительных функций», таких как наследование. Вероятно, для них было бы технически возможно поддерживать какой-то ограниченный тип наследования (не полиморфизм, из-за того, что они находятся в стеке), но я считаю, что это также выбор дизайна, чтобы не поддерживать наследование (как и многие другие вещи в языках .NET).

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

8
ответ дан 24 November 2019 в 01:39
поделиться

Структуры действительно поддерживают интерфейсы, поэтому таким образом можно делать некоторые полиморфные вещи.

1
ответ дан 24 November 2019 в 01:39
поделиться

IL - это стековый язык, поэтому вызов метода с аргументом происходит примерно так:

  1. Помещать аргумент в стек
  2. Вызвать метод.

Когда метод запускается, он извлекает из стека несколько байтов, чтобы получить свой аргумент. Он знает точно , сколько байтов нужно извлечь, потому что аргумент является либо указателем ссылочного типа (всегда 4 байта на 32-битном), либо типом значения, размер которого всегда точно известен.

Если это указатель ссылочного типа, тогда метод ищет объект в куче и получает дескриптор его типа, который указывает на таблицу методов, которая обрабатывает этот конкретный метод для этого точного типа. Если это тип значения, то поиск в таблице методов не требуется, поскольку типы значений не поддерживают наследование, поэтому существует только одна возможная комбинация метода / типа.

Если бы типы значений поддерживали наследование, тогда возникли бы дополнительные накладные расходы, так как конкретный тип структуры должен был бы быть помещен в стек вместе с ее значением, что означало бы своего рода поиск в таблице методов для конкретного конкретного экземпляра тип. Это устранило бы преимущества скорости и эффективности типов значений.

0
ответ дан 24 November 2019 в 01:39
поделиться

Класс, подобный наследованию, невозможно, поскольку структура накладывается непосредственно на стек. Наследующая структура будет больше, чем родительская, но JIT этого не знает и пытается разместить слишком много на слишком маленьком пространстве. Звучит немного непонятно, давайте напишем пример:

struct A {
    int property;
} // sizeof A == sizeof int

struct B : A {
    int childproperty;
} // sizeof B == sizeof int * 2

Если бы это было возможно, он бы вылетел из следующего фрагмента:

void DoSomething(A arg){};

...

B b;
DoSomething(b);

Пространство выделяется для размера A, а не для размера B.

3
ответ дан 24 November 2019 в 01:39
поделиться

Есть момент, который я хотел бы исправить. Хотя структуры не могут быть унаследованы, потому что они живут в стеке, это правильное объяснение, но в то же время это наполовину правильное объяснение. Структуры, как и любой другой тип значения , могут находиться в стеке. Поскольку это будет зависеть от того, где объявлена ​​переменная, они будут находиться либо в стеке , либо в куче . Это будет, когда они являются локальными переменными или полями экземпляра соответственно.

Сказав, что Сесил имеет имя, это правильно.

Я хотел бы это подчеркнуть, типы значений могут находиться в стеке. Это не значит, что они всегда так делают. Локальные переменные, включая параметры метода, будут. Все остальные не будут. Тем не менее, это все еще причина, по которой они не могут быть переданы по наследству. : -)

3
ответ дан 24 November 2019 в 01:39
поделиться

Structs are allocated on the stack. This means the value semantics are pretty much free, and accessing struct members is very cheap. This doesn't prevent polymorphism.

You could have each struct start with a pointer to its virtual function table. This would be a performance issue (every struct would be at least the size of a pointer), but it's doable. This would allow virtual functions.

What about adding fields?

Well, when you allocate a struct on the stack, you allocate a certain amount of space. The required space is determined at compile time (whether ahead of time or when JITting). If you add fields and then assign to a base type:

struct A
{
    public int Integer1;
}

struct B : A
{
    public int Integer2;
}

A a = new B();

This will overwrite some unknown part of the stack.

The alternative is for the runtime to prevent this by only writing sizeof(A) bytes to any A variable.

What happens if B overrides a method in A and references its Integer2 field? Either the runtime throws a MemberAccessException, or the method instead accesses some random data on the stack. Neither of these is permissible.

It's perfectly safe to have struct inheritance, so long as you don't use structs polymorphically, or so long as you don't add fields when inheriting. But these aren't terribly useful.

3
ответ дан 24 November 2019 в 01:39
поделиться

Этот вопрос кажется очень частым. Мне хочется добавить, что типы значений хранятся «на месте», где вы объявляете переменную; помимо деталей реализации, это означает, что отсутствует заголовок объекта, который что-то говорит об объекте, только переменная знает, какие данные там находятся.

2
ответ дан 24 November 2019 в 01:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: