Подтвердить что ты не робот

Союзы в качестве базового класса

Стандарт определяет, что Unions не могут использоваться в качестве базового класса, но есть ли какие-либо конкретные аргументы для этого? Насколько я понимаю, Unions могут иметь конструкторы, деструкторы, также переменные-члены и методы для работы с этими varibales. Короче говоря, Союз может инкапсулировать тип данных и состояние, к которым можно получить доступ через функции-члены. Таким образом, он в большинстве распространенных терминов квалифицируется как класс, и если он может действовать как класс, то почему он ограничен от действия в качестве базового класса?

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

4b9b3361

Ответ 1

Тони Парк дал ответ, который очень близок к истине. Комитет С++ в принципе не думал, что стоит усилий, чтобы сделать профсоюзы сильной частью С++, подобно обработке массивов как наследие, которое нам нужно было наследовать от C, но не очень хотелось.

У профсоюзов есть проблемы: если мы разрешаем типы non-POD в объединениях, как они создаются? Это, безусловно, можно сделать, но не обязательно безопасно, и для любого рассмотрения потребуются ресурсы комитета. И конечный результат был бы менее чем удовлетворительным, потому что то, что действительно требуется на разумном языке, - это дискриминационные союзы, а простые профсоюзы C никогда не могут быть подняты до дискриминационных союзов, совместимых с C (что я могу себе представить, так или иначе).

Разрабатывать технические проблемы: поскольку вы можете обернуть объединение POD только в структуру, не теряя ничего, нет преимущества, позволяющего объединениям в качестве баз. С компонентами соединения только POD нет проблем с явными конструкторами, просто назначающими один из компонентов или с использованием битблица (memcpy) для созданного компилятором конструктора (или назначения) копии.

Такие союзы, однако, недостаточно полезны, чтобы беспокоиться, кроме как сохранить их, поэтому существующий C-код можно считать допустимым С++. Эти POD-единственные соединения разбиты на С++, потому что они не могут сохранить жизненно важный инвариант, который они имеют в C: любой тип данных может использоваться как тип компонента.

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

Из этого следует, что необходимо инициализировать компонент объединения с mem-initialisers, например:

union X { string a; string b; X(string q) : a(q) {} };

Но теперь вопрос: что такое правило? Обычно правило состоит в том, что вы должны инициализировать каждый член и базу класса, если вы не делаете этого явно, конструктор по умолчанию используется для остатка, и если один тип, который явно не инициализирован, не имеет конструктора по умолчанию, он Ошибка [Исключение: конструкторы копирования, по умолчанию это конструктор экземпляра-члена].

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

Итак, теперь возникает вопрос: как бы вы написали, скажем, конструктор копирования? Разумеется, вполне возможно сделать и получить право, если вы создадите свой союз таким образом, скажем, созданы союзы событий X-Windows: с дискриминационным тегом в каждом компоненте, но вам придется использовать оператор размещения new для его выполнения, и вам придется сломать правило, которое я написал выше, которое на первый взгляд появилось правильно!

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

Существуют и другие случаи, когда вы можете определить компонент извне и использовать новое место размещения для управления соединением извне, но это не конструктор копирования. Дело в том, что если у вас есть N компонентов, вам нужны N конструкторов, а у С++ есть некорректная идея о том, что конструкторы используют имя класса, что оставляет вас довольно короткими именами и заставляет вас использовать типы phantom, чтобы позволить перегрузке выбирать правый конструктор.. и вы не можете сделать это для конструктора копирования, так как его подпись исправлена.

Хорошо, так есть альтернативы? Наверное, да, но их не так легко придумать, и труднее убедить более 100 человек, о которых стоит подумать на трехдневном собрании, переполненном другими проблемами.

Жаль, что комитет не выполнил вышеприведенное правило: профсоюзы являются обязательными для выравнивания произвольных данных, а внешнее управление компонентами на самом деле не так сложно сделать вручную, а тривиально и полностью безопасно, когда код генерируется подходящий алгоритм, другими словами, правило обязательноеесли вы хотите использовать С++ в качестве целевого языка компилятора и по-прежнему создавать читаемый переносимый код. Такие объединения с конструктивными членами имеют много применений, но наиболее важным является представление фрейма стека функции, содержащей вложенные блоки: каждый блок имеет локальные данные в структуре, а каждая структура является объединенным компонентом, нет необходимости в каких-либо конструкторах или такой, компилятор будет просто использовать новое место размещения. Союз обеспечивает выравнивание и размер, а также свободный доступ к компонентам. [И нет другого подходящего способа получить правильное выравнивание!]

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

Ответ 2

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

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

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

Ответ 3

Bjarne Stroustrup сказал, что "в этом есть мало причин для этого" в справочном руководстве The Annotated С++.

Ответ 4

Я думаю, что вы получили ответ сами в своих комментариях на ответ EJP.

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

Учитывая, что союзы в любом случае довольно опасны и не очень полезны, огромные новые возможности для создания ошибок, которые наследуются от профсоюзов, создаются, вероятно, просто не выглядели как хорошая идея:-)

Ответ 5

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

Нет никаких технических причин, по которым профсоюзы не могут быть базовым классом; это просто не разрешено. Разумная интерпретация будет состоять в том, чтобы думать о том, что союз является структурой, члены которой потенциально перекрываются в памяти, и рассматривают производный класс как класс, который наследует эту (довольно нечетную) структуру. Если вам нужна эта функциональность, вы обычно можете убедить большинство компиляторов принять анонимный союз в качестве члена структуры. Вот пример, который подходит для использования в качестве базового класса. (И там есть анонимная структура в союзе для хорошей меры.)

struct V3 {
    union {
        struct {
            float x,y,z;
        };
        float f[3];
    };
};

Обоснование профсоюзов как производного класса, вероятно, проще: результат не будет объединением. Союзы должны были объединиться со всеми их членами и всеми их базами. Это достаточно справедливо и может открыть некоторые интересные возможности шаблонов, но у вас будет ряд ограничений (все базы и члены должны быть POD), и вы сможете наследовать дважды, потому что производный тип по своей сути не является -POD?), Этот тип наследования будет отличаться от другого типа языковых видов спорта (ОК, а не то, что это остановило С++ раньше), и в любом случае это будет лишний избыток - существующая функциональность объединения будет делать то же самое.

Страуструп говорит об этом в книге D & E:

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

(Элизия не меняет смысл.)

Итак, я предполагаю, что решение произвольно, и он просто не видел причин менять функциональность объединения (он отлично работает как-то с подмножеством C на С++), и поэтому не создавал никакой интеграции с новыми функциями С++, И когда ветер изменился, он застрял таким образом.

Ответ 6

Вот моя догадка для С++ 03.

В соответствии с $9.5/1, в С++ 03, Unions не могут иметь виртуальные функции. Весь смысл значимого вывода состоит в том, чтобы переопределить поведение в производном классе. Если объединение не может иметь виртуальных функций, это означает, что нет смысла извлекать из объединения.

Следовательно, правило.

Ответ 7

Вы можете наследовать макет данных объединения, используя функцию анонимного объединения из С++ 11.

#include <cstddef>

template <size_t N,typename T>
struct VecData {
  union {
    struct {
      float x;
      float y;
      float z;
    };
    float a[N];
  };
};

template <size_t N, typename T>
  class Vec : public VecData<N,T> {
    //methods..
};

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